about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--emacs.el1931
1 files changed, 1031 insertions, 900 deletions
diff --git a/emacs.el b/emacs.el index f6cec48..73e0e8b 100644 --- a/emacs.el +++ b/emacs.el
@@ -1,73 +1,92 @@
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"
4;;
5;; License: GPLv3 3;; License: GPLv3
6 4
7;;; Commentary: 5(setopt custom-file (locate-user-emacs-file "custom.el"))
6(load custom-file :noerror)
8 7
9;; This is my Emacs configuration. There are many like it but this 8(add-hook 'after-init-hook
10;; one is mine. 9 (lambda () (load (locate-user-emacs-file "private") :noerror)))
11;;
12;; For the tenth time!
13 10
14;;; Code: 11(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
12(package-initialize)
15 13
16(defun sync/ (&optional file-name) 14(defun package-ensure (pkg &optional local require)
17 (expand-file-name (or file-name "") "~/sync")) 15 "Ensure PACKAGE is installed.
16If LOCAL is t, add ~/src/PKG.el to `load-path' and generate autoloads.
17If REQUIRE is non-nil, require it as well."
18 (cond
19 ((stringp local)
20 (unless (file-directory-p local)
21 (user-error "Package directory does not exist: %s" local))
22 (let ((autoload-file (expand-file-name (format "%s-autoloads.el" pkg) local))
23 (backup-inhibited t))
24 (add-to-list 'load-path local)
25 (loaddefs-generate local autoload-file)
26 (load autoload-file nil t)))
27 (local
28 (package-ensure pkg (expand-file-name (format "~/src/%s.el" pkg)) require))
29 (:else
30 (unless (package-installed-p pkg)
31 (unless (ignore-errors (package-install pkg))
32 (package-refresh-contents)
33 (package-install pkg)))))
34 (when require
35 (require pkg))
36 pkg)
18 37
19(add-hook 'after-init-hook 38(defmacro setf/assoc (alist key val &optional keep)
20 (lambda () 39 "Set KEY to VAL in ALIST using `assoc'/`equal' semantics.
21 (load (sync/ "private")))) 40Written as a convenience to writing out this long `alist-get'
41call every time. If VAL is nil, the entry with KEY is removed from ALIST unless
42KEEP is also non-nil."
43 `(setf (alist-get ,key ,alist nil ,(not keep) #'equal)
44 ,val))
22 45
23;;; Definitions: 46(defmacro comment (&rest _) (declare (indent defun)) nil)
24 47
25(defmacro inhibit-messages (&rest body) 48(package-ensure 'crux)
26 "Inhibit all messages in BODY." 49(crux-reopen-as-root-mode)
27 (declare (indent defun))
28 `(cl-letf (((symbol-function 'message) #'ignore))
29 ,@body))
30 50
31(defmacro comment (message &rest _ignore) 51(crux-with-region-or-buffer indent-region)
32 "Comment out lisp forms. 52(crux-with-region-or-buffer tabify)
33MESSAGE is for documentation purposes." 53(crux-with-region-or-buffer untabify)
34 (declare (indent defun))
35 t)
36 54
37(defmacro uncomment (message &rest body) 55(keymap-global-set "C-c i" #'crux-find-user-init-file)
38 "Uncomment a commented form." 56
39 (declare (indent defun)) 57
40 `(progn ,@body)) 58;;; Theme
41 59
42(comment 60(setopt modus-themes-bold-constructs nil)
43 (defun autoload-keymap (keymap library &optional parent-keymap) 61(setopt modus-themes-italic-constructs t)
44 "Require LIBRARY to load KEYMAP-SYMBOL, then press the buttons again. 62(setopt modus-themes-variable-pitch-ui t)
45If PARENT-KEYMAP is given, map KEYMAP within it; otherwise, use 63(setopt modus-themes-disable-other-themes t)
46`current-global-map'. This rips off `use-package-autoload-keymap' 64
47basically." 65(tool-bar-mode -1)
48 (lambda () (interactive) 66(menu-bar-mode -1)
49 (unless (featurep library) 67(scroll-bar-mode -1)
50 (require (cond ((symbolp library) library) 68(tooltip-mode -1)
51 ((stringp library) (intern library)) 69
52 (t (user-error "LIBRARY should be a symbol or string: %s" 70(setopt read-answer-short t)
53 library))))) 71(setopt use-dialog-box nil)
54 (let ((kv (this-command-keys-vector))) 72(setopt use-file-dialog nil)
55 (define-key (or parent-keymap (current-global-map)) 73(setopt use-short-answers t)
56 kv 74
57 (symbol-value keymap)) 75(setopt inhibit-startup-screen t)
58 (setq unread-command-events 76(setopt initial-buffer-choice t)
59 (mapcar (lambda (ev) (cons t ev)) 77(setopt initial-scratch-message ";; Emacs!\n\n")
60 (listify-key-sequence kv))))))) 78
61 79(setopt x-underline-at-descent-line t)
62(defun autoload-keymap (keymap-symbol package) 80(setopt blink-cursor-delay 0.25)
63 (require package) 81(setopt blink-cursor-interval 0.25)
64 (let ((kv (this-command-keys-vector))) 82(setopt blink-cursor-blinks 4)
65 (global-set-key kv (symbol-value keymap-symbol)) 83
66 (setq unread-command-events 84(define-advice startup-echo-area-message (:override ())
67 (mapcar (lambda (ev) (cons t ev)) 85 (if (get-buffer "*Warnings*")
68 (listify-key-sequence kv))))) 86 ";_;"
69 87 "^_^"))
70(defun reset-faces () 88
89(defun reset-faces (&rest _)
71 (dolist (face '(font-lock-regexp-face 90 (dolist (face '(font-lock-regexp-face
72 font-lock-builtin-face 91 font-lock-builtin-face
73 font-lock-variable-name-face 92 font-lock-variable-name-face
@@ -95,86 +114,28 @@ basically."
95 (face-spec-set face '((t :foreground unspecified 114 (face-spec-set face '((t :foreground unspecified
96 :background unspecified 115 :background unspecified
97 :slant italic)))) 116 :slant italic))))
98 (face-spec-set 'font-lock-comment-face 117 (when-let ((current (cl-loop for modus in '(modus-vivendi modus-operandi)
99 `((t :foreground ; :inherit doesn't work for some reason?? 118 if (memq modus custom-enabled-themes)
100 ,(face-foreground font-lock-doc-markup-face))))) 119 return modus
101 120 finally return nil)))
102(defun electric-pair-local-mode-disable () 121 (face-spec-set 'font-lock-comment-face
103 "Disable `electric-pair-mode', locally." 122 `((t :foreground ; :inherit doesn't work for some reason??
104 (electric-pair-local-mode -1)) 123 ,(if (eq current 'modus-operandi)
105 124 "#7c318f"
106(defun kill-this-buffer (&optional buffer-or-name) 125 "#caa6df"))))))
107 "Kill this buffer, or BUFFER-OR-NAME. 126(advice-add 'load-theme :after #'reset-faces)
108When called interactvely, the user will be prompted when passing
109\\[universal-argument]."
110 (interactive "P")
111 (cond
112 ((bufferp buffer-or-name)
113 (kill-buffer buffer-or-name))
114 ((null buffer-or-name)
115 (kill-current-buffer))
116 (:else
117 (kill-buffer (read-buffer "Kill: " nil :require-match)))))
118 127
119(defun define-org-capture-template (description &rest args) 128(load-theme 'modus-vivendi :no-confirm :no-enable)
120 "Define an template for `org-capture-templates'. 129(load-theme 'modus-operandi :no-confirm :no-enable)
121Will not replace an existing template unless `:force' in ARGS is 130(if (and (executable-find "darkman")
122non-nil. ARGS is a plist, which in addition to the additional 131 (let ((stat (shell-command "darkman get")))
123options `org-capture-templates' accepts (which see), also accepts 132 (and (= stat 0)
124the following: `:keys', `:description', `:type', `:target', and 133 (equal (with-current-buffer shell-command-buffer-name
125`:template'." 134 (buffer-substring (point-min) (point-max)))
126 (declare (indent 1)) 135 "dark\n"))))
127 (let* ((keys (plist-get args :keys)) 136 (load-theme 'modus-vivendi :no-confirm)
128 (type (plist-get args :type)) 137 (load-theme 'modus-operandi :no-confirm))
129 (target (plist-get args :target))
130 (template (plist-get args :template))
131 (force (plist-get args :force))
132 (template-value
133 (append
134 (list description)
135 (when (or type target template)
136 (list (or type 'entry) target template))
137 (cl-loop for i from 0 below (length args) by 2
138 unless (member (nth i args)
139 '( :keys :description :type
140 :target :template))
141 append (list (nth i args)
142 (plist-get args (nth i args)))))))
143 (if (seq-find (lambda (el) (equal (car el) keys))
144 org-capture-templates)
145 (and force
146 (setf (alist-get keys org-capture-templates nil nil #'equal)
147 template-value))
148 (setf org-capture-templates
149 (append org-capture-templates
150 (list (cons keys template-value)))))
151 org-capture-templates))
152
153(defun other-window-or-switch-buffer (&optional arg)
154 "Switch to the other window.
155If a window is the only buffer on a frame, switch buffer. When
156run with \\[universal-argument], unconditionally switch buffer."
157 (interactive "P")
158 (if (or arg (one-window-p))
159 (switch-to-buffer (other-buffer) nil t)
160 (other-window 1)))
161
162(defun delete-window-or-bury-buffer ()
163 "Delete the current window or bury its buffer.
164If the current window is the only window in the frame, bury its
165buffer instead."
166 (interactive)
167 (unless (ignore-errors (delete-window) t)
168 (bury-buffer)))
169 138
170(defun cycle-spacing@ (&optional n)
171 ;; `cycle-spacing' is wildly different in 29.1 over 28.
172 "Negate N argument on `cycle-spacing'.
173That is, with a positive N, deletes newlines as well, leaving -N
174spaces. If N is negative, it will not delete newlines and leave N
175spaces."
176 (interactive "*p")
177 (cycle-spacing (- n)))
178 139
179(defun first-frame@set-fonts () 140(defun first-frame@set-fonts ()
180 (remove-hook 'server-after-make-frame-hook 141 (remove-hook 'server-after-make-frame-hook
@@ -223,288 +184,44 @@ spaces."
223 (setopt tab-bar-show t) 184 (setopt tab-bar-show t)
224 (tab-bar-mode)) 185 (tab-bar-mode))
225 186
226(defvar no-tabs-modes '(emacs-lisp-mode 187(defun run-after-init-or-first-frame (func)
227 lisp-mode 188 "Run FUNC after init or after the first frame."
228 scheme-mode 189 (if (daemonp)
229 python-mode 190 (add-hook 'server-after-make-frame-hook func)
230 haskell-mode) 191 (add-hook 'after-init-hook func)))
231 "Modes /not/ to indent with tabs.")
232
233(defun indent-tabs-mode-maybe ()
234 (if (apply #'derived-mode-p no-tabs-modes)
235 (indent-tabs-mode -1)
236 (indent-tabs-mode 1)))
237
238(define-minor-mode truncate-lines-mode
239 "Buffer-local mode to toggle `truncate-lines'."
240 :lighter ""
241 (setq-local truncate-lines truncate-lines-mode))
242
243;;; Region or buffer stuff
244
245(defun call-with-region-or-buffer (fn &rest _r)
246 "Call function FN with current region or buffer.
247Good to use for :around advice."
248 ;; This `interactive' form is needed to override the advised function's form,
249 ;; to avoid errors when the region isn't active. This means that FN must take
250 ;; 2 arguments, the beginning and the end of the region to act on.
251 (interactive)
252 (if (region-active-p)
253 (funcall fn (region-beginning) (region-end))
254 (funcall fn (point-min) (point-max))))
255
256(defun delete-trailing-whitespace-except-current-line ()
257 (save-excursion
258 (delete-trailing-whitespace (point-min)
259 (line-beginning-position))
260 (delete-trailing-whitespace (line-end-position)
261 (point-max))))
262
263(defun create-missing-directories ()
264 "Automatically create missing directories."
265 (let ((target-dir (file-name-directory buffer-file-name)))
266 (unless (file-exists-p target-dir)
267 (make-directory target-dir :parents))))
268
269
270(defun vc-remote-off ()
271 "Turn VC off when remote."
272 (when (file-remote-p (buffer-file-name))
273 (setq-local vc-handled-backends nil)))
274
275(defun +titlecase-sentence-style-dwim (&optional arg)
276 "Titlecase a sentence.
277With prefix ARG, toggle the value of
278`titlecase-downcase-sentences' before sentence-casing."
279 (interactive "P")
280 (let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences)
281 titlecase-downcase-sentences)))
282 (titlecase-dwim 'sentence)))
283
284(defun +titlecase-org-headings ()
285 (interactive)
286 (require 'org)
287 (save-excursion
288 (goto-char (point-min))
289 ;; See also `org-map-tree'. I'm not using that function because I want to
290 ;; skip the first headline. A better solution would be to patch
291 ;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc).
292 (let ((level (funcall outline-level))
293 (org-special-ctrl-a/e t))
294 (while (and (progn (outline-next-heading)
295 (> (funcall outline-level) level))
296 (not (eobp)))
297 (titlecase-region (progn (org-beginning-of-line) (point))
298 (progn (org-end-of-line) (point)))))))
299
300(defcustom browse-url-safe-browser-functions nil
301 "\"Safe\" browser functions."
302 :type '(repeat-function))
303
304(defun browse-url-browser-function-safe-p (fn)
305 "Return t if FN is a \"safe\" browser function."
306 (memq fn (append browse-url-safe-browser-functions
307 (mapcar (lambda (i)
308 (plist-get (cdr i) :value))
309 (seq-filter (lambda (i)
310 (eq (car i) 'function-item))
311 (cdr (get 'browse-url-browser-function
312 'custom-type)))))))
313 192
314(put 'browse-url-browser-function 'safe-local-variable 193(run-after-init-or-first-frame #'first-frame@set-fonts)
315 'browse-url-browser-function-safe-p)
316 194
317 195(defun tab-bar-end-space ()
318;;; Packages: 196 `((end menu-item " " ignore)))
319
320(require 'package)
321(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
322(package-initialize)
323
324(defun ensure-package (pkg &optional local require)
325 "Esnure PKG is installed from repositories.
326If LOCAL is t, add ~/src/PKG.el to `load-path'.
327If LOCAL is a string, add that directory to the `load-path'.
328If REQUIRE is a non-nil value, require the package after adding it."
329 (cond
330 ((stringp local)
331 (cond
332 ((file-exists-p local)
333 (add-to-list 'load-path local))
334 (t (user-error "File does not exist: %s" local))))
335 (local
336 (ensure-package pkg
337 (expand-file-name
338 (format "~/src/%s.el"
339 (symbol-name pkg)))
340 require))
341 (:else
342 (unless (package-installed-p pkg)
343 (unless (ignore-errors (package-install pkg))
344 (package-refresh-contents)
345 (package-install pkg)))))
346 (when require
347 (require pkg))
348 pkg)
349
350;; Install packages here. Acutal configuration is done in the Configuration
351;; section.
352(ensure-package 'consult nil t)
353(ensure-package 'marginalia nil t)
354(ensure-package 'visual-fill-column)
355(ensure-package 'adaptive-wrap)
356(ensure-package 'avy)
357(ensure-package 'zzz-to-char)
358(ensure-package 'hungry-delete)
359(ensure-package 'undohist)
360(ensure-package 'jinx)
361(ensure-package 'markdown-mode)
362(ensure-package 'anzu)
363
364;; Local packages
365(ensure-package 'scule t)
366(ensure-package 'frowny t)
367(ensure-package 'mode-line-bell t)
368(ensure-package 'titlecase t)
369
370;;; Jabber
371(ensure-package 'jabber t t)
372
373(setopt jabber-chat-buffer-format "*%n*")
374(setopt jabber-browse-buffer-format "*%n*")
375(setopt jabber-groupchat-buffer-format "*%n*")
376(setopt jabber-muc-private-buffer-format "*%n*")
377
378(face-spec-set 'jabber-activity-face
379 '((t :inherit jabber-chat-prompt-foreign
380 :foreground unspecified
381 :weight normal)))
382(face-spec-set 'jabber-activity-personal-face
383 '((t :inherit jabber-chat-prompt-local
384 :foreground unspecified
385 :weight bold)))
386(face-spec-set 'jabber-chat-prompt-local
387 '((t :inherit minibuffer-prompt
388 :foreground unspecified
389 :weight normal
390 :slant italic)))
391(face-spec-set 'jabber-chat-prompt-foreign
392 '((t :inherit warning
393 :foreground unspecified
394 :weight normal)))
395(face-spec-set 'jabber-chat-prompt-system
396 '((t :inherit font-lock-doc-face
397 :foreground unspecified)))
398(face-spec-set 'jabber-rare-time-face
399 '((t :inherit font-lock-comment-face
400 :foreground unspecified
401 :underline nil)))
402
403(setopt jabber-auto-reconnect t)
404(setopt jabber-last-read-marker " ")
405(setopt jabber-muc-decorate-presence-patterns
406 '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil)
407 ("Mode #.*" . jabber-muc-presence-dim)
408 ("." . jabber-muc-presence-dim)))
409(setopt jabber-activity-make-strings #'jabber-activity-make-strings-shorten)
410(setopt jabber-rare-time-format
411 (format " - - - - - %%H:%d %%F"
412 (let ((min (string-to-number (format-time-string "%M"))))
413 (* 5 (floor min 5)))))
414(setopt jabber-muc-header-line-format '(" " jabber-muc-topic))
415
416(setopt jabber-groupchat-prompt-format "%n. ")
417(setopt jabber-chat-local-prompt-format "%n. ")
418(setopt jabber-chat-foreign-prompt-format "%n. ")
419(setopt jabber-muc-private-foreign-prompt-format "%g/%n. ")
420
421(defun jabber-connect-all* (&optional arg)
422 "Connect to all defined jabber accounts.
423If called with ARG non-nil, or with \\[universal-argument],
424disconnect first."
425 (interactive "P")
426 (when arg (jabber-disconnect))
427 (jabber-connect-all))
428
429(keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to)
430(with-eval-after-load 'jabber
431 (require 'jabber-httpupload nil t)
432 (map-keymap (lambda (key command)
433 (define-key jabber-global-keymap (vector (+ key #x60)) command))
434 jabber-global-keymap)
435 (keymap-global-set "C-x C-j" #'dired-jump))
436(keymap-global-set "C-x C-j" #'dired-jump)
437(keymap-set jabber-global-keymap "c" #'jabber-connect-all*)
438
439(with-eval-after-load 'dired
440 (keymap-set dired-mode-map "C-j" #'dired-up-directory))
441
442(keymap-global-set "C-c j" jabber-global-keymap)
443
444(remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo)
445(remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo)
446(add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons)
447(add-hook 'jabber-chat-mode-hook 'visual-line-mode)
448
449(add-hook 'jabber-chat-mode-hook
450 (defun jabber-no-position ()
451 (setq-local mode-line-position nil)))
452(add-hook 'jabber-activity-mode-hook
453 (defun jabber-activity-no-global-mode ()
454 (setq global-mode-string
455 (delete '(t jabber-activity-mode-string)
456 global-mode-string))))
457(add-hook 'jabber-alert-muc-hooks
458 (defun jabber@highlight-acdw (&optional _ _ buf _ _)
459 (when buf
460 (with-current-buffer buf
461 (let ((regexp (rx word-boundary
462 "acdw" ; maybe get from the config?
463 word-boundary)))
464 (hi-lock-unface-buffer regexp)
465 (highlight-regexp regexp 'jabber-chat-prompt-local))))))
466
467(when (fboundp 'jabber-chat-update-focus)
468 (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus))
469
470
471;;; Configuration:
472
473(setopt custom-file (locate-user-emacs-file "custom.el"))
474(load custom-file :noerror)
475
476;;; General keybinding changes
477
478(keymap-global-set "M-o" #'other-window-or-switch-buffer)
479(keymap-global-set "C-x 0" #'delete-window-or-bury-buffer)
480(keymap-global-set "M-SPC" #'cycle-spacing@)
481(keymap-global-set "M-u" #'universal-argument)
482(keymap-set universal-argument-map "M-u" #'universal-argument-more)
483 197
484;;; Theme 198(add-to-list 'tab-bar-format 'tab-bar-format-align-right :append)
199(add-to-list 'tab-bar-format 'tab-bar-format-global :append)
200(add-to-list 'tab-bar-format 'tab-bar-end-space :append)
485 201
486(if (daemonp) 202(add-hook 'dired-mode-hook #'hl-line-mode)
487 (add-hook 'server-after-make-frame-hook #'first-frame@set-fonts) 203(with-eval-after-load 'org-agenda
488 (run-with-idle-timer 1 nil #'first-frame@set-fonts)) 204 (add-hook 'org-agenda-mode-hook #'hl-line-mode))
489 205
490(tool-bar-mode -1) 206(with-eval-after-load 'tabulated-list
207 (add-hook 'tabulated-list-mode-hook #'hl-line-mode))
491 208
492(setopt modus-themes-bold-constructs nil) 209(setopt echo-keystrokes 0.01)
493(setopt modus-themes-italic-constructs t)
494(setopt modus-themes-variable-pitch-ui t)
495(setopt modus-themes-disable-other-themes t)
496 210
497(add-hook 'modus-themes-after-load-theme-hook #'reset-faces) 211(setopt switch-to-buffer-in-dedicated-window 'pop)
498(add-hook 'after-init-hook #'reset-faces) 212(setopt switch-to-buffer-obey-display-actions t)
499 213
500(load-theme 'modus-vivendi :no-confirm :no-enable) 214(when (package-ensure 'adaptive-wrap)
501(load-theme 'modus-operandi :no-confirm) 215 (add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode))
502 216
503(add-hook 'text-mode-hook #'visual-line-mode) 217;;; Mode-line
504(add-hook 'prog-mode-hook #'auto-fill-mode)
505(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode)
506 218
507;;; Mode line 219(defun hide-minor-mode (mode &optional hook)
220 "Hide MODE from the mode-line.
221HOOK is used to trigger the action, and defaults to MODE-hook."
222 (setf (alist-get mode minor-mode-alist) (list ""))
223 (add-hook (intern (or hook (format "%s-hook" mode)))
224 (lambda () (hide-minor-mode mode))))
508 225
509(setq mode-line-modes 226(setq mode-line-modes
510 (let ((recursive-edit-help-echo 227 (let ((recursive-edit-help-echo
@@ -547,140 +264,251 @@ mouse-3: Toggle minor modes"
547 (" (" (:eval (string-trim vc-mode)) ")")) 264 (" (" (:eval (string-trim vc-mode)) ")"))
548 " " 265 " "
549 (mode-line-position 266 (mode-line-position
550 (" - " mode-line-position)) 267 (" ∙ " mode-line-position))
551 " - " 268 " ∙ "
552 mode-line-modes ; the one above 269 mode-line-modes ; the one above
553 mode-line-misc-info 270 mode-line-misc-info
554 mode-line-end-spaces))) 271 mode-line-end-spaces)))
555 272
556;; Remove modes from mode-line 273
557(defun hide-minor (mode &optional hook) 274;;; Completion and minibuffer
558 "Hide MODE from the mode line.
559HOOK defaults to MODE-hook, and is used to trigger the hiding."
560 (setf (alist-get mode minor-mode-alist) (list ""))
561 (add-hook (intern (or hook (format "%s-hook" mode)))
562 (lambda ()
563 (setf (alist-get mode minor-mode-alist) (list "")))))
564 275
565(hide-minor 'frowny-mode) 276(setopt tab-always-indent 'complete)
566(hide-minor 'whitespace-mode) 277;; (setopt completion-styles '(basic partial-completion substring flex))
567(hide-minor 'hungry-delete-mode)
568 278
569(scroll-bar-mode -1) 279(setopt completion-ignore-case t)
570(menu-bar-mode -1) 280(setopt read-buffer-completion-ignore-case t)
281(setopt read-file-name-completion-ignore-case t)
282(setopt completion-flex-nospace t)
571 283
572(add-hook 'prog-mode-hook #'indent-tabs-mode-maybe) 284;; These aren't /that/ useful if you're not using *Completions*.
285(setopt completions-detailed t)
286(setopt completion-auto-help 'visible)
287(setopt completion-auto-select 'second-tab)
288(setopt completions-header-format nil)
289(setopt completions-format 'one-column)
290
291(progn
292 (keymap-set minibuffer-local-map "C-p"
293 #'minibuffer-previous-completion)
294 (keymap-set minibuffer-local-map "C-n"
295 #'minibuffer-next-completion)
296 (keymap-set completion-in-region-mode-map "C-p"
297 #'minibuffer-previous-completion)
298 (keymap-set completion-in-region-mode-map "C-n"
299 #'minibuffer-next-completion))
300
301(setf/assoc display-buffer-alist
302 "\\*Completions\\*"
303 '((display-buffer-reuse-mode-window)))
573 304
574(setopt electric-pair-skip-whitespace 'chomp) 305(setopt enable-recursive-minibuffers t)
575(electric-pair-mode) 306(minibuffer-depth-indicate-mode)
307(minibuffer-electric-default-mode)
576 308
577(setopt sh-basic-offset tab-width) 309(setopt file-name-shadow-properties '(invisible t intangible t))
310(file-name-shadow-mode)
578 311
579(keymap-set emacs-lisp-mode-map "C-c C-c" #'eval-defun) 312(define-minor-mode truncate-lines-local-mode
580(keymap-set emacs-lisp-mode-map "C-c C-k" #'eval-buffer) 313 "Toggle `truncate-lines' in the current buffer."
581(keymap-set lisp-interaction-mode-map "C-c C-c" #'eval-defun) 314 :lighter ""
582(keymap-set lisp-interaction-mode-map "C-c C-k" #'eval-buffer) 315 (setq-local truncate-lines truncate-lines-local-mode))
583 316
584(advice-add 'indent-region :around #'call-with-region-or-buffer) 317(add-hook 'completion-list-mode-hook #'truncate-lines-local-mode)
585(advice-add 'tabify :around #'call-with-region-or-buffer) 318(add-hook 'minibuffer-setup-hook #'truncate-lines-local-mode)
586(advice-add 'untabify :around #'call-with-region-or-buffer) 319
587 320(when (package-ensure 'orderless)
588(keymap-global-set "M-=" #'count-words) 321 (setopt completion-styles '(substring orderless basic))
589 322 (setopt completion-category-overrides
590;;; Geiser & Scheme 323 '((file (styles basic partial-completion)))))
591 324
592(ensure-package 'geiser) 325(when (package-ensure 'consult nil t)
593(when (executable-find "csi") 326 (keymap-global-set "C-x b" #'consult-buffer)
594 (ensure-package 'geiser-chicken)) 327 (keymap-global-set "C-x 4 b" #'consult-buffer-other-window)
595(setopt scheme-program-name (or (executable-find "csi") 328 (keymap-global-set "C-x 5 b" #'consult-buffer-other-frame)
596 "scheme")) 329 (keymap-global-set "C-x r b" #'consult-bookmark)
597(with-eval-after-load 'scheme 330 (keymap-global-set "M-y" #'consult-yank-pop)
598 (keymap-unset scheme-mode-map "M-o" t) 331 (keymap-global-set "M-g g" #'consult-goto-line)
599 ;; Comparse "keywords" --- CHICKEN (http://wiki.call-cc.org/eggref/5/comparse) 332 (keymap-global-set "M-g M-g" #'consult-goto-line)
600 (put 'sequence* 'scheme-indent-function 1) 333 (keymap-global-set "M-g o" #'consult-outline)
601 (put 'satisfies 'scheme-indent-function 1) 334 (keymap-global-set "M-g m" #'consult-mark)
602 (add-hook 'scheme-mode-hook #'geiser-mode)) 335 (keymap-global-set "M-g i" #'consult-imenu)
336 (keymap-global-set "M-s d" #'consult-find)
337 (keymap-global-set "M-s D" #'consult-locate)
338 (keymap-global-set "M-s g" #'consult-grep)
339 (keymap-global-set "M-s G" #'consult-git-grep)
340 (keymap-global-set "M-s r" #'consult-ripgrep)
341 (keymap-global-set "M-s l" #'consult-line)
342 (keymap-global-set "M-s k" #'consult-keep-lines)
343 (keymap-global-set "M-s u" #'consult-focus-lines)
344
345 (keymap-global-set "M-s e" #'consult-isearch-history)
346 (keymap-set isearch-mode-map "M-e" #'consult-isearch-history)
347 (keymap-set isearch-mode-map "M-s e" #'consult-isearch-history)
348 (keymap-set isearch-mode-map "M-s l" #'consult-line)
349
350 (setopt xref-show-xrefs-function #'consult-xref)
351 (setopt xref-show-definitions-function #'xref-show-definitions-completing-read)
352
353 (setopt consult-preview-key "M-.")
354
355 (consult-customize
356 consult-ripgrep consult-git-grep consult-grep
357 consult-xref
358 :preview-key '(:debounce 0.4 any)))
359
360(when (package-ensure 'marginalia)
361 (marginalia-mode))
603 362
604(setopt gieser-autodoc-delay 0.1) 363(setopt history-length t)
364(setopt history-delete-duplicates t)
365(setopt savehist-save-minibuffer-history t)
366(setopt savehist-autosave-interval 5)
367(savehist-mode)
605 368
369
370;;; Text editing
606 371
607(with-eval-after-load 'geiser-mode 372(setopt fill-column 80)
608 (keymap-set geiser-mode-map "C-c C-k" #'geiser-eval-buffer-and-go) 373(global-so-long-mode)
609 (keymap-unset geiser-mode-map "C-." t)
610 (add-hook 'geiser-repl-startup-hook
611 (defun geiser-add-default-directory-to-load-path ()
612 (geiser-add-to-load-path default-directory))))
613 374
614;;; Visual fill column 375(defun cycle-spacing* (&optional n)
376 "Negate N argument on `cycle-spacing'."
377 (interactive "*p")
378 (cycle-spacing (- n)))
379(keymap-global-set "M-SPC" #'cycle-spacing*)
380
381(when (package-ensure 'hungry-delete)
382 (setopt hungry-delete-chars-to-skip " \t")
383 (setopt hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip))
384 (setopt hungry-delete-join-reluctantly nil)
385 (with-eval-after-load 'hungry-delete
386 (add-to-list 'hungry-delete-except-modes 'eshell-mode)
387 (add-to-list 'hungry-delete-except-modes 'nim-mode)
388 (add-to-list 'hungry-delete-except-modes 'python-mode)
389 (hide-minor-mode 'hungry-delete-mode))
390 (global-hungry-delete-mode))
391
392(setopt isearch-lazy-count t)
393(setopt isearch-regexp-lax-whitespace t)
394(setopt isearch-wrap-pause 'no)
395(setopt search-default-mode t)
396(setopt search-whitespace-regexp ".*?") ; swiper-style
397(setopt search-ring-max 256)
398(setopt regexp-search-ring-max 256)
399
400(define-advice isearch-cancel (:before () add-to-history)
401 "Add search string to history when canceling isearch."
402 (unless (string-equal "" isearch-string)
403 (isearch-update-ring isearch-string isearch-regexp)))
404
405(define-advice perform-replace (:around (fn &rest args) dont-exit-on-anykey)
406 "Don't exit replace for anykey that's not in `query-replace-map'."
407 (save-window-excursion
408 (cl-letf* ((lookup-key-orig
409 (symbol-function 'lookup-key))
410 ((symbol-function 'lookup-key)
411 (lambda (map key &optional accept-default)
412 (or (apply lookup-key-orig map key accept-default)
413 (when (eq map query-replace-map) 'help)))))
414 (apply fn args))))
415
416(when (package-ensure 'isearch-mb)
417 (with-eval-after-load 'isearch-mb
418 (with-eval-after-load 'consult
419 (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history)
420 (keymap-set isearch-mb-minibuffer-map "M-r" #'consult-isearch-history)
421 (add-to-list 'isearch-mb--after-exit #'consult-line)
422 (keymap-set isearch-mb-minibuffer-map "M-s l" #'consult-line))
423 (with-eval-after-load 'anzu
424 (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace)
425 (keymap-set isearch-mb-minibuffer-map "M-%"
426 #'anzu-isearch-query-replace)))
427 (isearch-mb-mode))
428
429(when (package-ensure 'anzu)
430 (setopt anzu-mode-lighter "")
431 (setopt anzu-deactivate-region t)
432 (keymap-global-set "M-%" #'anzu-query-replace-regexp)
433 (keymap-global-set "C-M-%" #'anzu-query-replace)
434 (keymap-set isearch-mode-map "M-%" #'anzu-isearch-query-replace-regexp)
435 (keymap-set isearch-mode-map "C-M-%" #'anzu-isearch-query-replace)
436 (global-anzu-mode))
615 437
616(with-eval-after-load 'visual-fill-column 438(keymap-global-set "M-/" #'hippie-expand)
617 (setopt visual-fill-column-center-text t) 439(keymap-global-set "C-x C-b" #'ibuffer)
618 (setopt visual-fill-column-width (+ fill-column 2))
619 (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust))
620(add-hook 'visual-line-mode-hook #'visual-fill-column-mode)
621(add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode)
622 440
623;;; Set major mode for non-file buffers 441(defun call-with-region-or-buffer (fn &rest _r)
442 "Call function FN with current region or buffer.
443Good to use for :around advice."
444 ;; This `interactive' form is needed to override the advised function's form,
445 ;; to avoid errors when the region isn't active. This means that FN must take
446 ;; 2 arguments, the beginning and the end of the region to act on.
447 (interactive)
448 (if (region-active-p)
449 (funcall fn (region-beginning) (region-end))
450 (funcall fn (point-min) (point-max))))
624 451
625(setopt major-mode 452(delete-selection-mode)
626 (lambda () ; guess major mode from buffer name
627 (unless buffer-file-name
628 (let ((buffer-file-name (buffer-name)))
629 (set-auto-mode)))))
630 453
631;; Dialogs 454(when (package-ensure 'avy)
632(unless (boundp 'use-short-answers) 455 (setopt avy-background t)
633 (fset 'yes-or-no-p 'y-or-n-p)) 456 (setopt avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm"))
457 (keymap-global-set "M-j" #'avy-goto-char-timer)
458 (keymap-set isearch-mode-map "M-j" #'avy-isearch))
634 459
635(setopt read-answer-short t) 460(when (package-ensure 'zzz-to-char)
636(setopt use-dialog-box nil) 461 (keymap-global-set "M-z"
637(setopt use-file-dialog nil) 462 (defun zzz-to-char* (arg)
638(setopt use-short-answers t) 463 (interactive "P")
639(require 'savehist) 464 (call-interactively
640(setopt history-length t) 465 (if arg #'zzz-to-char #'zzz-to-char-up-to-char)))))
641(setopt history-delete-duplicates t)
642(setopt savehist-save-minibuffer-history t)
643(setopt savehist-autosave-interval 30)
644(savehist-mode)
645 466
646;; Killing and yanking 467;;; Prose
647(setopt kill-do-not-save-duplicates t)
648(setopt kill-read-only-ok t)
649;; XXX: This setting causes an error message the first time it's called:
650;; "Selection owner couldn't convert: TIMESTAMP". I have absolutely no idea why
651;; I get this error, but it's generated in `x_get_foreign_selection'. I also
652;; can't inhibit the message or do anything else with it, so for now, I'll just
653;; live with the message.
654(setopt save-interprogram-paste-before-kill t)
655(setopt yank-pop-change-selection t)
656(delete-selection-mode)
657 468
658;; Notifying the user 469(add-hook 'text-mode-hook #'visual-line-mode)
659(setopt echo-keystrokes 0.01)
660(setopt ring-bell-function #'ignore)
661 470
662;; Point and mark 471(when (package-ensure 'olivetti)
663(setopt set-mark-command-repeat-pop t) 472 (add-hook 'text-mode-hook #'olivetti-mode))
473
474(when (package-ensure 'jinx)
475 (add-hook 'text-mode-hook #'jinx-mode)
476 (with-eval-after-load 'jinx
477 (keymap-set jinx-mode-map "M-$" #'jinx-correct)
478 (keymap-set jinx-mode-map "C-M-$" #'jinx-languages)))
479
480(defun org-fk-region (start end)
481 "Get the Flesch-Kincaid score of an `org-mode' region."
482 (interactive "r")
483 (let ((buf (get-buffer-create "*fk*" t)))
484 (shell-command-on-region start end
485 "pandoc -t plain -f org | ~/src/fk/fk.perl"
486 buf)
487 (with-current-buffer buf
488 (buffer-substring-no-properties (point-min) (- (point-max) 1)))
489 (kill-buffer buf)))
664 490
665;; The system 491(crux-with-region-or-buffer org-fk-region)
666(setopt read-process-output-max (* 10 1024 1024))
667 492
668;; Startup 493(when (package-ensure 'scule t t)
669(setopt inhibit-startup-screen t) 494 (keymap-global-set "M-c" scule-map))
670(setopt initial-buffer-choice t)
671(setopt initial-scratch-message nil)
672 495
673(define-advice startup-echo-area-message (:override ()) 496(when (package-ensure 'titlecase t)
674 (if (get-buffer "*Warnings*") 497 (keymap-set scule-map "M-t" #'titlecase-dwim))
675 ";_;"
676 "^_^"))
677 498
678;; Text editing 499(setopt dictionary-default-popup-strategy "lev") ; Levenshtein distance 1
679(setopt fill-column 80) 500(setopt dictionary-server "dict.org")
680(setopt sentence-end-double-space nil) 501(setopt dictionary-use-single-buffer t)
681(setopt tab-width 8) 502(keymap-global-set "M-#"
682(setopt tab-always-indent 'complete) 503 (defun dictionary-lookup-dwim ()
683(global-so-long-mode) 504 (interactive)
505 (unless (ignore-errors (dictionary-lookup-definition))
506 (call-interactively #'dictionary-search))))
507
508;;; Programming
509
510(setopt electric-pair-skip-whitespace 'chomp)
511(electric-pair-mode)
684 512
685(setopt show-paren-delay 0.01) 513(setopt show-paren-delay 0.01)
686(setopt show-paren-style 'parenthesis) 514(setopt show-paren-style 'parenthesis)
@@ -688,46 +516,73 @@ HOOK defaults to MODE-hook, and is used to trigger the hiding."
688(setopt show-paren-when-point-inside-paren t) 516(setopt show-paren-when-point-inside-paren t)
689(show-paren-mode) 517(show-paren-mode)
690 518
691;; Encodings 519(add-hook 'prog-mode-hook #'auto-fill-mode)
692(set-language-environment "UTF-8") 520(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode)
693(setopt buffer-file-coding-system 'utf-8-unix) 521(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
694(setopt coding-system-for-read 'utf-8-unix)
695(setopt coding-system-for-write 'utf-8-unix)
696(setopt default-process-coding-system '(utf-8-unix . utf-8-unix))
697(setopt locale-coding-system 'utf-8-unix)
698(set-charset-priority 'unicode)
699(prefer-coding-system 'utf-8-unix)
700(set-default-coding-systems 'utf-8-unix)
701(set-terminal-coding-system 'utf-8-unix)
702(set-keyboard-coding-system 'utf-8-unix)
703(pcase system-type
704 ((or 'ms-dos 'windows-nt)
705 (set-clipboard-coding-system 'utf-16-le)
706 (set-selection-coding-system 'utf-16-le))
707 (_
708 (set-selection-coding-system 'utf-8)
709 (set-clipboard-coding-system 'utf-8)))
710 522
711(setopt x-underline-at-descent-line t) 523(when (package-ensure 'dumb-jump)
712(setopt blink-cursor-delay 0.25) 524 (add-hook 'xref-backend-functions #'dumb-jump-xref-activate))
713(setopt blink-cursor-interval 0.25) 525
714(setopt blink-cursor-blinks 1) 526(add-hook 'prog-mode-hook #'prettify-symbols-mode)
527
528(keymap-global-set "M-:" #'pp-eval-expression)
529
530;; Tabs
531
532(setopt tab-width 8)
533
534(defvar space-indent-modes '(emacs-lisp-mode
535 lisp-mode
536 scheme-mode
537 python-mode
538 haskell-mode
539 text-mode)
540 "Modes to indent with spaces, not tabs.")
541
542(defun indent-tabs-mode-maybe ()
543 (setq indent-tabs-mode
544 (if (apply #'derived-mode-p space-indent-modes) nil t)))
545(add-hook 'prog-mode-hook #'indent-tabs-mode-maybe)
546
547(setopt sh-basic-offset tab-width)
548(setopt perl-indent-level tab-width)
549
550;; Scheme
551
552(when (package-ensure 'geiser)
553 (when (executable-find "csi")
554 (package-ensure 'geiser-chicken))
555 (setopt scheme-program-name (or (executable-find "csi")
556 "scheme"))
557 (add-hook 'scheme-mode-hook #'geiser-mode))
558
559(require 'autoinsert)
560(setf/assoc auto-insert-alist
561 "\\.scm"
562 '(nil "#!/bin/sh" \n
563 "#| -*- scheme -*-" \n
564 "exec csi -ss \"$0\" \"$@\"" \n
565 _ \n
566 "|#" \n \n))
567
568
569;;; Files
715 570
716;; Files
717(setopt auto-revert-verbose nil) 571(setopt auto-revert-verbose nil)
718(setopt global-auto-revert-non-file-buffers t) 572(setopt global-auto-revert-non-file-buffers t)
573(global-auto-revert-mode)
574
719(setopt create-lockfiles nil) 575(setopt create-lockfiles nil)
720(setopt find-file-visit-truename t)
721(setopt mode-require-final-newline t) 576(setopt mode-require-final-newline t)
722(setopt view-read-only t) 577(setopt view-read-only t)
723(setopt save-silently t) 578(setopt save-silently t)
724(global-auto-revert-mode)
725 579
726(setopt auto-save-default nil) 580(setopt auto-save-default nil)
727(setopt auto-save-interval 1)
728(setopt auto-save-no-message t) 581(setopt auto-save-no-message t)
729(setopt auto-save-timeout 1) 582(setopt auto-save-interval 2)
730(setopt auto-save-visited-interval 1) 583(setopt auto-save-timeout 2)
584(setopt auto-save-visited-interval 2)
585(setopt remote-file-name-inhibit-auto-save t)
731(setopt remote-file-name-inhibit-auto-save-visited t) 586(setopt remote-file-name-inhibit-auto-save-visited t)
732(add-to-list 'auto-save-file-name-transforms 587(add-to-list 'auto-save-file-name-transforms
733 `(".*" ,(locate-user-emacs-file "auto-save/") t)) 588 `(".*" ,(locate-user-emacs-file "auto-save/") t))
@@ -735,99 +590,217 @@ HOOK defaults to MODE-hook, and is used to trigger the hiding."
735 590
736(setopt backup-by-copying t) 591(setopt backup-by-copying t)
737(setopt version-control t) 592(setopt version-control t)
738(setopt kept-new-versions 8) 593(setopt kept-new-versions 3)
739(setopt kept-old-versions 8) 594(setopt kept-old-versions 3)
740(setopt delete-old-versions t) 595(setopt delete-old-versions t)
741(setq-default backup-directory-alist 596(add-to-list 'backup-directory-alist '("^/dev/shm/" . nil))
742 `(("^/dev/shm" . nil) 597(add-to-list 'backup-directory-alist '("^/tmp/" . nil))
743 ("^/tmp" . nil) 598(when-let ((xrd (getenv "XDG_RUNTIME_DIR")))
744 (,(getenv "XDG_RUNTIME_DIR") . nil) 599 (add-to-list 'backup-directory-alist (cons xrd nil)))
745 ("." . ,(locate-user-emacs-file "backup")))) 600(add-to-list 'backup-directory-alist
746 601 (cons "." (locate-user-emacs-file "backup/"))
747(require 'recentf) 602 :append)
748(setopt recentf-max-menu-items 500) 603
604(setopt recentf-max-menu-items 100)
749(setopt recentf-max-saved-items nil) 605(setopt recentf-max-saved-items nil)
750(setopt recentf-auto-cleanup 'mode)
751(setopt recentf-case-fold-search t) 606(setopt recentf-case-fold-search t)
752;; (add-to-list 'recentf-exclude etc/) 607(with-eval-after-load 'recentf
753(add-to-list 'recentf-exclude "-autoloads.el\\'") 608 (add-to-list 'recentf-exclude "-autoloads.el\\'"))
754(add-hook 'buffer-list-update-hook #'recentf-track-opened-file) 609(add-hook 'buffer-list-update-hook #'recentf-track-opened-file)
755(add-hook 'after-save-hook #'recentf-save-list) 610(add-hook 'after-save-hook #'recentf-save-list)
756(recentf-mode) 611(recentf-mode)
757 612
758(require 'saveplace)
759(setopt save-place-forget-unreadable-files (eq system-type 'gnu/linux)) 613(setopt save-place-forget-unreadable-files (eq system-type 'gnu/linux))
760(save-place-mode) 614(save-place-mode)
761 615
762(require 'uniquify) 616;; Encodings
763(setopt uniquify-after-kill-buffer-p t) 617(set-language-environment "UTF-8")
764(setopt uniquify-buffer-name-style 'forward) 618(setopt buffer-file-coding-system 'utf-8-unix)
765(setopt uniquify-ignore-buffers-re "^\\*") 619(setopt coding-system-for-read 'utf-8-unix)
766(setopt uniquify-separator path-separator) 620(setopt coding-system-for-write 'utf-8-unix)
621(setopt default-process-coding-system '(utf-8-unix . utf-8-unix))
622(setopt locale-coding-system 'utf-8-unix)
623(set-charset-priority 'unicode)
624(prefer-coding-system 'utf-8-unix)
625(set-default-coding-systems 'utf-8-unix)
626(set-terminal-coding-system 'utf-8-unix)
627(set-keyboard-coding-system 'utf-8-unix)
628(pcase system-type
629 ((or 'ms-dos 'windows-nt)
630 (set-clipboard-coding-system 'utf-16-le)
631 (set-selection-coding-system 'utf-16-le))
632 (_
633 (set-selection-coding-system 'utf-8)
634 (set-clipboard-coding-system 'utf-8)))
635
636;; Undo
637(when (package-ensure 'undohist)
638 (undohist-initialize))
639
640;;; ...
641
642(when (package-ensure 'embark nil t)
643 (when (package-installed-p 'consult)
644 (package-ensure 'embark-consult nil t))
645 (keymap-global-set "C-." #'embark-act)
646 (keymap-global-set "M-." #'embark-dwim)
647 (keymap-global-set "C-h B" #'embark-bindings)
648 (setf/assoc display-buffer-alist
649 "\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
650 '(nil (window-parameters (mode-line-format . none))))
651 (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode))
652
653(setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
654(setopt eldoc-idle-delay 0.01)
655
656(setopt recenter-positions '(top middle bottom))
657
658(defmacro inhibit-messages (&rest body)
659 "Inhibit all messages in BODY."
660 (declare (indent defun))
661 `(cl-letf (((symbol-function 'message) #'ignore))
662 ,@body))
663
664(add-hook 'find-file-not-found-functions
665 (defun create-missing-directories ()
666 "Automatically create missing directories."
667 (let ((target-dir (file-name-directory buffer-file-name)))
668 (unless (file-exists-p target-dir)
669 (make-directory target-dir :parents)))))
767 670
768(setopt vc-follow-symlinks t) 671(setopt vc-follow-symlinks t)
769(setopt vc-make-backup-files t) 672(setopt vc-make-backup-files t)
673(add-hook 'find-file-hook
674 (defun vc-remote-off ()
675 "Turn VC off when remote."
676 (when (file-remote-p (buffer-file-name))
677 (setq-local vc-handled-backends nil))))
678(with-eval-after-load 'vc-dir
679 (add-hook 'vc-dir-mode-hook #'hl-line-mode))
770 680
771;; Whitespace
772(require 'whitespace)
773(setopt whitespace-style '(face trailing tabs tab-mark)) 681(setopt whitespace-style '(face trailing tabs tab-mark))
774(global-whitespace-mode) 682(global-whitespace-mode)
775(add-hook 'before-save-hook #'delete-trailing-whitespace-except-current-line) 683(hide-minor-mode 'whitespace-mode)
684(add-hook 'before-save-hook
685 (defun delete-trailing-whitespace-except-current-line ()
686 (save-excursion
687 (delete-trailing-whitespace (point-min)
688 (line-beginning-position))
689 (delete-trailing-whitespace (line-end-position)
690 (point-max)))))
776 691
777;; Native compilation 692(defcustom browse-url-safe-browser-functions nil
778(setopt native-comp-async-report-warnings-errors 'silent) 693 "\"Safe\" browser functions."
779(setopt native-comp-deferred-compilation t) 694 :type '(repeat-function))
780(setopt native-compile-target-directory (locate-user-emacs-file "eln"))
781(when (boundp 'native-comp-eln-load-path)
782 (add-to-list 'native-comp-eln-load-path native-compile-target-directory))
783(when (fboundp 'startup-redirect-eln-cache)
784 (startup-redirect-eln-cache native-compile-target-directory))
785
786(global-goto-address-mode)
787 695
788;; Winner 696(defun browse-url-browser-function-safe-p (fn)
789(winner-mode) 697 "Return t if FN is a \"safe\" browser function."
698 (memq fn (append browse-url-safe-browser-functions
699 (mapcar (lambda (i)
700 (plist-get (cdr i) :value))
701 (seq-filter (lambda (i)
702 (eq (car i) 'function-item))
703 (cdr (get 'browse-url-browser-function
704 'custom-type)))))))
790 705
791;;; Hooks 706(put 'browse-url-browser-function 'safe-local-variable
707 'browse-url-browser-function-safe-p)
792 708
793(defcustom persist-settings-hook nil 709(defun list-of-strings-p (x)
794 "Functions to run in order to persist settings." 710 "Is X a list of strings?"
795 :type 'hook) 711 (and x
712 (listp x)
713 (cl-every #'stringp x)))
796 714
797(defun persist-settings () 715(put 'ispell-local-words 'safe-local-variable
798 (inhibit-messages 716 'list-of-strings-p)
799 (run-with-idle-timer 5 nil #'run-hooks 'persist-settings-hook)))
800 717
801(defvar persist-timer 718(package-ensure '0x0) ; TODO: write my own package for rsync
802 (run-with-timer nil 60 #'persist-settings) 719
803 "Timer running `persist-settings-hook'.") 720(when (package-ensure 'electric-cursor t)
721 (hide-minor-mode 'electric-cursor-mode)
722 (setopt electric-cursor-alist '((overwrite-mode . (hbar . 8))
723 (t . box)))
724 (electric-cursor-mode))
725
726(defun fill-double-space-sentences-region (start end)
727 "Fill from START to END, double-spacing sentences."
728 (let ((sentence-end-double-space t))
729 (repunctuate-sentences :no-query start end)
730 (fill-region start end)))
731
732(defun fill-or-unfill-region (start end)
733 "Fill or unfill from START to END."
734 (let ((filled-p (cl-every (lambda (ln) (<= (length ln) fill-column))
735 (string-split (buffer-substring start end)
736 "[\n\r]+"))))
737 (if filled-p
738 (let ((fill-column most-positive-fixnum)
739 (fill-paragraph-function nil))
740 (message "Unfilling region")
741 (fill-double-space-sentences-region start end))
742 (message "Filling region")
743 (fill-double-space-sentences-region start end))))
744
745(defun fill-or-unfill-dwim ()
746 (interactive)
747 (save-mark-and-excursion
748 (if (region-active-p)
749 (fill-or-unfill-region (region-beginning)
750 (region-end))
751 (fill-or-unfill-region (progn (backward-paragraph)
752 (point))
753 (progn (forward-paragraph)
754 (point))))))
804 755
805(add-hook 'persist-settings-hook #'save-place-kill-emacs-hook) 756(keymap-global-set "M-q" #'fill-or-unfill-dwim)
806(add-hook 'persist-settings-hook #'bookmark-exit-hook-internal)
807(with-eval-after-load 'em-hist
808 (add-hook 'persist-settings-hook #'eshell-save-some-history))
809(with-eval-after-load 'prescient
810 (add-hook 'persist-settings-hook #'prescient--save))
811 757
812(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) 758;; Fix annoying error messages when I type the <FN> key
813(add-hook 'find-file-not-found-functions #'create-missing-directories) 759(keymap-global-set "<0x100811d0>" #'ignore) ; Keychron
814(add-hook 'find-file-hook #'vc-remote-off) 760(keymap-global-set "<WakeUp>" #'ignore) ; Laptop
815(add-hook 'dired-mode-hook #'hl-line-mode)
816(add-hook 'org-agenda-mode-hook #'hl-line-mode)
817 761
818;;; Tab bar 762(keymap-global-set "M-u" #'universal-argument)
763(keymap-set universal-argument-map "M-u" #'universal-argument-more)
819 764
820(defun tab-bar-end-space () 765(defun kill-buffer-dwim (&optional buffer-or-name)
821 `((end menu-item " " ignore))) 766 "Kill BUFFER-OR-NAME or the current buffer."
767 (interactive "P")
768 (cond
769 ((bufferp buffer-or-name)
770 (kill-buffer buffer-or-name))
771 ((null buffer-or-name)
772 (kill-current-buffer))
773 (:else
774 (kill-buffer (read-buffer "Kill: " nil :require-match)))))
775(keymap-global-set "C-x C-k" #'kill-buffer-dwim)
776
777(defun other-window-dwim (&optional arg)
778 "Switch to another window/buffer.
779Calls `other-window', which see, unless
780- the current window is alone on its frame
781- `other-window-dwim' is called with \\[universal-argument]
782In these cases, switch to the last-used buffer."
783 (interactive "P")
784 (if (or arg (one-window-p))
785 (switch-to-buffer (other-buffer) nil t)
786 (other-window 1)))
787(keymap-global-set "M-o" #'other-window-dwim)
822 788
823(add-to-list 'tab-bar-format 'tab-bar-format-align-right :append) 789(defun delete-window-dwim ()
824(add-to-list 'tab-bar-format 'tab-bar-format-global :append) 790 "Delete the current window or bury its buffer.
825(add-to-list 'tab-bar-format 'tab-bar-end-space :append) 791If the current window is alone in its frame, bury the buffer
792instead."
793 (interactive)
794 (unless (ignore-errors (delete-window) t)
795 (bury-buffer)))
796(keymap-global-set "C-x 0" #'delete-window-dwim)
826 797
798
827;;; Org mode 799;;; Org mode
828 800
829(keymap-global-set "C-c a" #'org-agenda) 801(keymap-global-set "C-c a" #'org-agenda)
830(keymap-global-set "C-c c" #'org-capture) 802(keymap-global-set "C-c c" #'org-capture)
803(keymap-global-set "C-c l" #'org-store-link)
831 804
832(setopt org-clock-clocked-in-display 'mode-line) 805(setopt org-clock-clocked-in-display 'mode-line)
833(setopt org-clock-out-remove-zero-time-clocks t) 806(setopt org-clock-out-remove-zero-time-clocks t)
@@ -845,69 +818,21 @@ HOOK defaults to MODE-hook, and is used to trigger the hiding."
845(setopt org-agenda-inhibit-startup t) 818(setopt org-agenda-inhibit-startup t)
846(setopt org-deadline-warning-days 0) 819(setopt org-deadline-warning-days 0)
847(setopt org-cycle-separator-lines 0) 820(setopt org-cycle-separator-lines 0)
848(setopt org-agenda-span 14) 821(setopt org-agenda-span 10)
849 822(setopt org-blank-before-new-entry '((heading . t)
850(add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree) 823 (plain-list-item . nil)))
851
852(defmacro org-insert-or-surround (character)
853 (let ((c (gensym)))
854 `(defun ,(intern (format "org-insert-or-surround-%s" character)) (arg)
855 ,(format "Insert %s or surround the region with it." character)
856 (interactive "p")
857 (let ((,c ,(if (stringp character)
858 (string-to-char character)
859 character)))
860 (if (org-region-active-p)
861 (let ((begin (region-beginning))
862 (end (region-end)))
863 (save-mark-and-excursion
864 (deactivate-mark)
865 (goto-char begin)
866 (self-insert-command arg ,c)
867 (goto-char (+ 1 end))
868 (self-insert-command arg ,c)))
869 (self-insert-command arg ,c))))))
870
871(with-eval-after-load 'org
872 (keymap-set org-mode-map "*" (org-insert-or-surround "*"))
873 (keymap-set org-mode-map "/" (org-insert-or-surround "/"))
874 (keymap-set org-mode-map "_" (org-insert-or-surround "_"))
875 (keymap-set org-mode-map "=" (org-insert-or-surround "="))
876 (keymap-set org-mode-map "~" (org-insert-or-surround "~"))
877 (keymap-set org-mode-map "+" (org-insert-or-surround "+")))
878
879;; Fix braindead behavior
880(with-eval-after-load 'org-mouse
881 (defun org--mouse-open-at-point (orig-fun &rest args)
882 (let ((context (org-context)))
883 (cond
884 ;; Don't org-cycle when clicking on headline stars. The biggest problem
885 ;; is that this function advises `org-open-at-point', so I can't C-c C-o
886 ;; from a headline star.
887 ;; ((assq :headline-stars context) (org-cycle))
888 ((assq :checkbox context) (org-toggle-checkbox))
889 ((assq :item-bullet context)
890 (let ((org-cycle-include-plain-lists t)) (org-cycle)))
891 ((org-footnote-at-reference-p) nil)
892 (t (apply orig-fun args))))))
893 824
894;;; Spelling 825(defvar-local org-agenda/setup-done nil)
895
896(defun list-of-strings-p (x)
897 "Is X a list of strings?"
898 (and x
899 (listp x)
900 (cl-every #'stringp x)))
901
902(put 'ispell-local-words 'safe-local-variable
903 'list-of-strings-p)
904 826
905(add-hook 'text-mode-hook #'jinx-mode) 827(add-hook 'org-agenda-after-show-hook
906(with-eval-after-load 'jinx 828 (defun org-agenda-after-show/setup ()
907 (keymap-set jinx-mode-map "M-$" #'jinx-correct) 829 (org-narrow-to-subtree)
908 (keymap-set jinx-mode-map "C-M-$" #'jinx-languages)) 830 (goto-char (point-min))
831 (unless org-agenda/setup-done
832 (run-hooks 'org-mode-hook))
833 (setq org-agenda/setup-done t)))
909 834
910;;; org-return-dwim 835;; org-return-dwim
911;; https://github.com/alphapapa/unpackaged.el, 836;; https://github.com/alphapapa/unpackaged.el,
912;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ 837;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/
913(defun org-return-dwim (&optional arg) 838(defun org-return-dwim (&optional arg)
@@ -1002,12 +927,36 @@ itself. Other values of ARG will call `newline' with that ARG."
1002 (org-table-copy-down (or n 1)) 927 (org-table-copy-down (or n 1))
1003 (org-return-dwim n))) 928 (org-return-dwim n)))
1004 929
930(defmacro org-insert-or-surround (character)
931 (let ((c (gensym)))
932 `(defun ,(intern (format "org-insert-or-surround-%s" character)) (arg)
933 ,(format "Insert %s or surround the region with it." character)
934 (interactive "p")
935 (let ((,c ,(if (stringp character)
936 (string-to-char character)
937 character)))
938 (if (org-region-active-p)
939 (let ((begin (region-beginning))
940 (end (region-end)))
941 (save-mark-and-excursion
942 (deactivate-mark)
943 (goto-char begin)
944 (self-insert-command arg ,c)
945 (goto-char (+ 1 end))
946 (self-insert-command arg ,c)))
947 (self-insert-command arg ,c))))))
948
1005(with-eval-after-load 'org 949(with-eval-after-load 'org
1006 (keymap-set org-mode-map "RET" #'org-return-dwim) 950 (keymap-set org-mode-map "RET" #'org-return-dwim)
1007 (keymap-set org-mode-map "S-<return>" #'org-table-copy-down|org-return-dwim)) 951 (keymap-set org-mode-map "S-<return>" #'org-table-copy-down|org-return-dwim)
1008 952 (keymap-set org-mode-map "*" (org-insert-or-surround "*"))
1009;;; Copy rich text to the keyboard 953 (keymap-set org-mode-map "/" (org-insert-or-surround "/"))
954 (keymap-set org-mode-map "_" (org-insert-or-surround "_"))
955 (keymap-set org-mode-map "=" (org-insert-or-surround "="))
956 (keymap-set org-mode-map "~" (org-insert-or-surround "~"))
957 (keymap-set org-mode-map "+" (org-insert-or-surround "+")))
1010 958
959;; Copy rich text to the keyboard
1011(defcustom clipboard-html-copy-program 960(defcustom clipboard-html-copy-program
1012 (if (or (equal "wayland" 961 (if (or (equal "wayland"
1013 (getenv "XDG_SESSION_TYPE")) 962 (getenv "XDG_SESSION_TYPE"))
@@ -1019,20 +968,6 @@ Should be a list of strings---the command line.
1019Defaults to 'wl-copy' on wayland and 'xclip' on Xorg." 968Defaults to 'wl-copy' on wayland and 'xclip' on Xorg."
1020 :type '(repeat string)) 969 :type '(repeat string))
1021 970
1022;; Thanks to Oleh Krehel:
1023;; https://emacs.stackexchange.com/questions/54292/copy-results-of-org-export-directly-to-clipboard
1024;; So. Emacs can't do this itself because it doesn't support sending clipboard
1025;; or selection contents as text/html. We have to use xclip instead.
1026;; (defun org-to-html-to-clipboard (&rest org-export-args)
1027;; "Export current org buffer to HTML, then copy it to the clipboard.
1028;; ORG-EXPORT-ARGS are passed to `org-export-to-file'."
1029;; (let ((f (make-temp-file "org-html-export")))
1030;; (apply #'org-export-to-file 'html f org-export-args)
1031;; (start-process "xclip" " *xclip*"
1032;; "xclip" "-verbose" "-i" f
1033;; "-t" "text/html" "-selection" "clipboard")
1034;; (message "HTML pasted to clipboard.")))
1035
1036(defun org-export-html-copy (&rest org-export-args) 971(defun org-export-html-copy (&rest org-export-args)
1037 "Export current org buffer to HTML and copy to clipboard as rich text. 972 "Export current org buffer to HTML and copy to clipboard as rich text.
1038ORG-EXPORT-ARGS are passed to `org-export-to-buffer'." 973ORG-EXPORT-ARGS are passed to `org-export-to-buffer'."
@@ -1050,161 +985,344 @@ ORG-EXPORT-ARGS are passed to `org-export-to-buffer'."
1050 (kill-buffer-and-window)) 985 (kill-buffer-and-window))
1051 (message "HTML copied to clipboard."))) 986 (message "HTML copied to clipboard.")))
1052 987
1053;; Wayland version.. TODO: make it work for both
1054;; (defun org-to-html-to-clipboard (&rest org-export-args)
1055;; "Export current org buffer to HTML, then copy it to the clipboard.
1056;; ORG-EXPORT-ARGS are passed to `org-export-to-file'."
1057;; (let ((buf (generate-new-buffer "*org-html-clipboard*" t)))
1058;; (apply #'org-export-to-buffer 'html buf org-export-args)
1059;; (with-current-buffer buf
1060;; (call-process-region (point-min) (point-max)
1061;; "wl-copy" nil nil nil
1062;; "-t" "text/html")
1063;; (kill-buffer-and-window))
1064;; (message "HTML copied to clipboard.")))
1065
1066(defun org-subtree-to-html-to-clipboard () 988(defun org-subtree-to-html-to-clipboard ()
1067 "Export current subtree to HTML." 989 "Export current subtree to HTML."
1068 (interactive) 990 (interactive)
1069 (org-export-html-copy nil :subtree)) 991 (org-export-html-copy nil :subtree))
1070 992
1071(undohist-initialize) 993;; (info "(org) Breaking Down Tasks")
1072 994(defun org-summary-todo (n-done n-not-done)
1073(require 'hungry-delete) 995 "Switch entry to DONE when all subentries are done, to TODO otherwise."
1074(setopt hungry-delete-chars-to-skip " \t") 996 (let (org-log-done org-log-states) ; turn off logging
1075(setopt hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip)) 997 (org-todo (if (= n-not-done 0) "DONE" "TODO"))))
1076(setopt hungry-delete-join-reluctantly nil) 998(add-hook 'org-after-todo-statistics-hook #'org-summary-todo)
1077(add-to-list 'hungry-delete-except-modes 'eshell-mode)
1078(add-to-list 'hungry-delete-except-modes 'nim-mode)
1079(add-to-list 'hungry-delete-except-modes 'python-mode)
1080(global-hungry-delete-mode)
1081
1082(setopt avy-background t)
1083(setopt avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm"))
1084(keymap-global-set "M-j" #'avy-goto-char-timer)
1085(keymap-set isearch-mode-map "M-j" #'avy-isearch)
1086(keymap-global-set "M-z" #'zzz-to-char)
1087
1088(marginalia-mode)
1089
1090(keymap-global-set "C-x b" #'consult-buffer)
1091(keymap-global-set "C-x 4 b" #'consult-buffer-other-window)
1092(keymap-global-set "C-x 5 b" #'consult-buffer-other-frame)
1093(keymap-global-set "C-x r b" #'consult-bookmark)
1094(keymap-global-set "M-y" #'consult-yank-pop)
1095(keymap-global-set "M-g g" #'consult-goto-line)
1096(keymap-global-set "M-g M-g" #'consult-goto-line)
1097(keymap-global-set "M-g o" #'consult-outline)
1098(keymap-global-set "M-g m" #'consult-mark)
1099(keymap-global-set "M-g i" #'consult-imenu)
1100(keymap-global-set "M-s d" #'consult-find)
1101(keymap-global-set "M-s D" #'consult-locate)
1102(keymap-global-set "M-s g" #'consult-grep)
1103(keymap-global-set "M-s G" #'consult-git-grep)
1104(keymap-global-set "M-s r" #'consult-ripgrep)
1105(keymap-global-set "M-s l" #'consult-line)
1106(keymap-global-set "M-s k" #'consult-keep-lines)
1107(keymap-global-set "M-s u" #'consult-focus-lines)
1108
1109(keymap-global-set "M-s e" #'consult-isearch-history)
1110(keymap-set isearch-mode-map "M-e" #'consult-isearch-history)
1111(keymap-set isearch-mode-map "M-s e" #'consult-isearch-history)
1112(keymap-set isearch-mode-map "M-s l" #'consult-line)
1113
1114(setopt xref-show-xrefs-function #'consult-xref)
1115(setopt xref-show-definitions-function #'consult-xref)
1116
1117(setopt consult-preview-key "M-.")
1118
1119(consult-customize
1120 consult-ripgrep consult-git-grep consult-grep
1121 consult-xref
1122 :preview-key '(:debounce 0.4 any))
1123
1124(setopt initial-scratch-message ";;; Emacs!\n\n")
1125
1126(keymap-global-set "C-x C-b" #'ibuffer)
1127(add-hook 'ibuffer-hook #'hl-line-mode)
1128 999
1129(require 'scule) 1000;; Fix braindead behavior
1130(keymap-global-set "M-c" scule-map) 1001(with-eval-after-load 'org-mouse
1131(autoload 'titlecase-dwim "titlecase" nil t) 1002 (defun org--mouse-open-at-point (orig-fun &rest args)
1132(keymap-set scule-map "M-t" #'titlecase-dwim) 1003 (let ((context (org-context)))
1133 1004 (cond
1134;; Use M-u for prefix keys 1005 ;; Don't org-cycle when clicking on headline stars. The biggest problem
1135(keymap-global-set "M-u" #'universal-argument) 1006 ;; is that this function advises `org-open-at-point', so I can't C-c C-o
1136(keymap-set universal-argument-map "M-u" #'universal-argument-more) 1007 ;; from a headline star.
1137 1008 ;; ((assq :headline-stars context) (org-cycle))
1138(autoload 'frowny-mode "frowny" nil t) 1009 ((assq :checkbox context) (org-toggle-checkbox))
1139(add-hook 'jabber-chat-mode-hook #'frowny-mode) 1010 ((assq :item-bullet context)
1140(add-hook 'jabber-chat-mode-hook #'electric-pair-local-mode-disable) 1011 (let ((org-cycle-include-plain-lists t)) (org-cycle)))
1141 1012 ((org-footnote-at-reference-p) nil)
1142(keymap-global-set "M-/" #'hippie-expand) 1013 (t (apply orig-fun args))))))
1143 1014
1144(setopt mode-line-bell-flash-time 0.25) 1015(defun define-org-capture-template (description &rest args)
1145(autoload 'mode-line-bell-mode "mode-line-bell" nil t) 1016 "Define an template for `org-capture-templates'.
1146(mode-line-bell-mode) 1017Will not replace an existing template unless `:force' in ARGS is
1018non-nil. ARGS is a plist, which in addition to the additional
1019options `org-capture-templates' accepts (which see), also accepts
1020the following: `:keys', `:description', `:type', `:target', and
1021`:template'."
1022 (declare (indent 1))
1023 (let* ((keys (plist-get args :keys))
1024 (type (plist-get args :type))
1025 (target (plist-get args :target))
1026 (template (plist-get args :template))
1027 (force (plist-get args :force))
1028 (template-value
1029 (append
1030 (list description)
1031 (when (or type target template)
1032 (list (or type 'entry) target template))
1033 (cl-loop for i from 0 below (length args) by 2
1034 unless (member (nth i args)
1035 '( :keys :description :type
1036 :target :template))
1037 append (list (nth i args)
1038 (plist-get args (nth i args)))))))
1039 (if (seq-find (lambda (el) (equal (car el) keys))
1040 org-capture-templates)
1041 (and force
1042 (setf (alist-get keys org-capture-templates nil nil #'equal)
1043 template-value))
1044 (setf org-capture-templates
1045 (append org-capture-templates
1046 (list (cons keys template-value)))))
1047 org-capture-templates))
1147 1048
1148(keymap-global-set "C-x C-k" #'kill-this-buffer) 1049(add-hook 'org-mode-hook
1050 (defun org-mode-line-position ()
1051 (setq-local mode-line-position
1052 '((:propertize
1053 ("" mode-line-percent-position)
1054 local-map mode-line-column-line-number-mode-map
1055 display (min-width (5.0)))
1056 (org-word-count-mode org-word-count-string))))
1057 (setq mode-line-misc-info
1058 (delete '(org-word-count-mode org-word-count-string)
1059 mode-line-misc-info)))
1060
1061;;; Org word count
1062;; also does Flesch-Kincaid reading level.
1063;; TODO: customization ... stuff.
1064
1065(defun fk-region (start end)
1066 (interactive "r")
1067 ;; (let* ((fk-buf (get-buffer-create " *fk*"))
1068 ;; (fk-proc
1069 ;; (start-process "fk" fk-buf "/home/acdw/src/fk/fk.perl")))
1070 ;; (set-process-sentinel fk-proc #'ignore)
1071 ;; (process-send-region fk-proc start end)
1072 ;; (process-send-eof fk-proc)
1073 ;; (with-current-buffer fk-buf
1074 ;; (goto-char (point-max))
1075 ;; (forward-line -1)
1076 ;; (string-chop-newline (buffer-substring-no-properties
1077 ;; (line-beginning-position) (point-max)))))
1078
1079 (let ((shell-command-buffer-name (format "*fk/%s*" (buffer-name))))
1080 (shell-command-on-region start end "~/src/fk/fk.perl")
1081 (with-current-buffer shell-command-buffer-name
1082 (buffer-substring-no-properties (point-min) (- (point-max) 1))))
1083 )
1084
1085(defun org-word-count-region (start end &optional interactive)
1086 (interactive "r\np")
1087 (when (derived-mode-p 'org-mode)
1088 (save-window-excursion
1089 (inhibit-messages
1090 (let ((shell-command-buffer-name (format " *wc/%s*" (buffer-name)))
1091 wc fk)
1092 (shell-command-on-region start end
1093 "pandoc -t plain -f org")
1094 (with-current-buffer shell-command-buffer-name
1095 (setq wc (count-words (point-min) (point-max)))
1096 (setq fk (string-to-number (fk-region (point-min) (point-max)))))
1097 (when interactive (message "%s" wc))
1098 (list wc fk))))))
1099
1100(defvar-local org-word-count-string ""
1101 "Number of words in buffer.")
1102
1103(defun update-org-word-count-string ()
1104 (when (derived-mode-p 'org-mode)
1105 (setq org-word-count-string
1106 (apply #'format " %dw/%.2ffk"
1107 (org-word-count-region (point-min) (point-max))))))
1108
1109(defvar org-word-count-timer nil
1110 "Timer for `org-word-count'.")
1111
1112(define-minor-mode org-word-count-mode
1113 "Count words and update the org-word-count-string."
1114 :lighter " owc"
1115 (cond
1116 ((and (derived-mode-p 'org-mode)
1117 org-word-count-mode)
1118 (unless (timerp org-word-count-timer)
1119 (setq org-word-count-timer
1120 (run-with-idle-timer 1 t #'update-org-word-count-string))))
1121 (:else
1122 (when (timerp org-word-count-timer)
1123 (cancel-timer org-word-count-timer))
1124 (setq org-word-count-timer nil)
1125 (setq org-word-count-mode nil))))
1126(hide-minor-mode 'org-word-count-mode)
1149 1127
1150(require 'anzu) 1128(add-hook 'org-mode-hook #'org-word-count-mode)
1151(global-anzu-mode)
1152(setopt search-default-mode t)
1153(setopt anzu-mode-lighter "")
1154(setopt anzu-deactivate-region t)
1155 1129
1156(global-set-key [remap query-replace] #'anzu-query-replace-regexp) 1130;;; Org recentering
1157(global-set-key [remap query-replace-regexp] #'anzu-query-replace)
1158(define-key isearch-mode-map [remap isearch-query-replace]
1159 #'anzu-isearch-query-replace-regexp)
1160(define-key isearch-mode-map [remap isearch-query-replace-regexp]
1161 #'anzu-isearch-query-replace)
1162 1131
1163;;; Completion & minibuffer 1132(defun org-recenter (&optional arg)
1133 (interactive "P")
1134 (if (or arg
1135 (eq last-command 'org-recenter))
1136 (recenter-top-bottom arg)
1137 (save-excursion
1138 (unless (org-at-heading-p)
1139 (ignore-errors (org-previous-visible-heading 1)))
1140 (recenter-top-bottom 0))))
1141(with-eval-after-load 'org
1142 (keymap-set org-mode-map "C-l" #'org-recenter))
1164 1143
1165(minibuffer-depth-indicate-mode) 1144;;; Org links -- extra types
1166(setopt tab-always-indent 'complete)
1167(file-name-shadow-mode)
1168(minibuffer-electric-default-mode)
1169 1145
1170(setopt completion-ignore-case t) 1146(with-eval-after-load 'ol
1171(setopt read-buffer-completion-ignore-case t) 1147 (org-link-set-parameters "tel" :follow #'ignore)
1172(setopt read-file-name-completion-ignore-case t) 1148 (org-link-set-parameters "sms" :follow #'ignore))
1173(setopt completions-detailed t)
1174(setopt enable-recursive-minibuffers t)
1175(setopt file-name-shadow-properties '(invisible t intangible t))
1176(setopt minibuffer-eldef-shorten-default t)
1177(setopt minibuffer-prompt-properties
1178 '(read-only t cursor-intangible t face minibuffer-prompt))
1179(setopt window-resize-pixelwise t)
1180(setopt frame-resize-pixelwise t)
1181 1149
1182(add-hook 'completion-list-mode-hook #'truncate-lines-mode) 1150
1183(add-hook 'minibuffer-setup-hook #'truncate-lines-mode) 1151;;; Jabber
1184 1152
1185(setopt completion-auto-help 'visible) 1153(when (package-ensure 'jabber t t)
1186(setopt completion-auto-select 'second-tab) 1154 (setopt jabber-chat-buffer-format "*%n*")
1187(setopt completions-header-formato nil) 1155 (setopt jabber-browse-buffer-format "*%n*")
1188;; Up/down when completing in the minibuffer 1156 (setopt jabber-groupchat-buffer-format "*%n*")
1189(keymap-set minibuffer-local-map "C-p" #'minibuffer-previous-completion) 1157 (setopt jabber-muc-private-buffer-format "*%n*")
1190(keymap-set minibuffer-local-map "C-n" #'minibuffer-next-completion) 1158
1191;; Up/down when competing in a normal buffer 1159 (face-spec-set 'jabber-activity-face
1192(keymap-set completion-in-region-mode-map "C-p" 1160 '((t :inherit jabber-chat-prompt-foreign
1193 #'minibuffer-previous-completion) 1161 :foreground unspecified
1194(keymap-set completion-in-region-mode-map "C-n" #'minibuffer-next-completion) 1162 :weight normal)))
1195(keymap-set completion-in-region-mode-map "RET" #'minibuffer-choose-completion) 1163 (face-spec-set 'jabber-activity-personal-face
1164 '((t :inherit jabber-chat-prompt-local
1165 :foreground unspecified
1166 :weight bold)))
1167 (face-spec-set 'jabber-chat-prompt-local
1168 '((t :inherit minibuffer-prompt
1169 :foreground unspecified
1170 :weight normal
1171 :slant italic)))
1172 (face-spec-set 'jabber-chat-prompt-foreign
1173 '((t :inherit warning
1174 :foreground unspecified
1175 :weight normal)))
1176 (face-spec-set 'jabber-chat-prompt-system
1177 '((t :inherit font-lock-doc-face
1178 :foreground unspecified)))
1179 (face-spec-set 'jabber-rare-time-face
1180 '((t :inherit font-lock-comment-face
1181 :foreground unspecified
1182 :underline nil)))
1183
1184 (setopt jabber-auto-reconnect t)
1185 (setopt jabber-last-read-marker
1186 "-------------------------------------------------------------------")
1187 (setopt jabber-muc-decorate-presence-patterns
1188 '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil)
1189 ("Mode #.*" . jabber-muc-presence-dim)
1190 ("." . jabber-muc-presence-dim)))
1191 (setopt jabber-activity-make-strings #'jabber-activity-make-strings-shorten)
1192 (setopt jabber-rare-time-format
1193 (format " - - - - - %%H:%d %%F"
1194 (let ((min (string-to-number (format-time-string "%M"))))
1195 (* 5 (floor min 5)))))
1196 (setopt jabber-muc-header-line-format '(" " jabber-muc-topic))
1197
1198 (setopt jabber-groupchat-prompt-format "%n. ")
1199 (setopt jabber-chat-local-prompt-format "%n. ")
1200 (setopt jabber-chat-foreign-prompt-format "%n. ")
1201 (setopt jabber-muc-private-foreign-prompt-format "%g/%n. ")
1202
1203 (defun jabber-connect-all* (&optional arg)
1204 "Connect to all defined jabber accounts.
1205If called with ARG non-nil, or with \\[universal-argument],
1206disconnect first."
1207 (interactive "P")
1208 (when arg (jabber-disconnect))
1209 (jabber-connect-all))
1210
1211 (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to)
1212 (with-eval-after-load 'jabber
1213 (require 'jabber-httpupload nil t)
1214 (map-keymap (lambda (key command)
1215 (define-key jabber-global-keymap (vector (+ key #x60)) command))
1216 jabber-global-keymap)
1217 (keymap-global-set "C-x C-j" #'dired-jump))
1218 (keymap-global-set "C-x C-j" #'dired-jump)
1219 (keymap-set jabber-global-keymap "c" #'jabber-connect-all*)
1220 (keymap-global-set "C-c j" jabber-global-keymap)
1221
1222 (remove-hook 'jabber-alert-muc-hooks #'jabber-muc-echo)
1223 (remove-hook 'jabber-alert-presence-hooks #'jabber-presence-echo)
1224 (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons)
1225 (add-hook 'jabber-chat-mode-hook #'olivetti-mode)
1226 (add-hook 'jabber-chat-mode-hook
1227 (defun jabber-chat-mode-no-position ()
1228 (setq-local mode-line-position nil)))
1229 (add-hook 'jabber-alert-muc-hooks
1230 (defun jabber@highlight-acdw (&optional _ _ buf _ _)
1231 (when buf
1232 (with-current-buffer buf
1233 (let ((regexp (rx word-boundary
1234 "acdw" ; maybe get from the config?
1235 word-boundary)))
1236 (hi-lock-unface-buffer regexp)
1237 (highlight-regexp regexp 'jabber-chat-prompt-local))))))
1238
1239 (add-hook 'jabber-chat-mode-hook
1240 (defun electric-pair-local-disable ()
1241 (electric-pair-local-mode -1)))
1242
1243 (when (fboundp 'jabber-chat-update-focus)
1244 (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus)))
1196 1245
1197;;; Other stuff 1246
1247;;; Dired
1198 1248
1199(comment "Unnecessary after being defined." 1249(with-eval-after-load 'dired
1200 (ensure-package 'wiki-abbrev t t) 1250 (keymap-set dired-mode-map "C-j" #'dired-up-directory))
1201 (wiki-abbrev-insinuate))
1202(add-hook 'text-mode-hook #'abbrev-mode)
1203 1251
1204;;; Browser 1252
1253;;; Browsing the web
1205 1254
1206(setopt browse-url-browser-function #'eww-browse-url) 1255(setopt browse-url-browser-function #'eww-browse-url)
1207 1256
1257;;; EWW
1258
1259(setopt eww-use-browse-url ".")
1260(setopt eww-auto-rename-buffer 'title)
1261(setopt eww-default-download-directory
1262 (or (xdg-user-dir "DOWNLOAD")
1263 "~/Downloads"))
1264(setopt eww-history-limit nil)
1265
1266;; Use Emacs bookmarks for EWW
1267(defun bookmark-eww--make ()
1268 "Make eww bookmark record."
1269 `((filename . ,(plist-get eww-data :url))
1270 (title . ,(plist-get eww-data :title))
1271 (time . ,(current-time-string))
1272 (handler . ,#'bookmark-eww-handler)
1273 (defaults . (,(concat
1274 ;; url without the https and path
1275 (replace-regexp-in-string
1276 "/.*" ""
1277 (replace-regexp-in-string
1278 "\\`https?://" ""
1279 (plist-get eww-data :url)))
1280 " - "
1281 ;; page title
1282 (replace-regexp-in-string
1283 "\\` +\\| +\\'" ""
1284 (replace-regexp-in-string
1285 "[\n\t\r ]+" " "
1286 (plist-get eww-data :title))))))))
1287
1288(defun bookmark-eww-handler (bm)
1289 "Handler for eww bookmarks."
1290 (eww-browse-url (alist-get 'filename bm)))
1291
1292(defun bookmark-eww--setup ()
1293 "Setup eww bookmark integration."
1294 (setq-local bookmark-make-record-function #'bookmark-eww--make))
1295(add-hook 'eww-mode-hook #'bookmark-eww--setup)
1296
1297(with-eval-after-load 'eww
1298 (define-key eww-mode-map "b" #'bookmark-set)
1299 (define-key eww-mode-map "B" #'bookmark-jump))
1300
1301;; Transforming URLs
1302;; `eww-transform-url' exists, but I like my package better.
1303
1304(when (package-ensure 'browse-url-transform t)
1305 (setopt browse-url-transform-alist
1306 `(;; Privacy-respecting alternatives
1307 ("twitter\\.com" . "nitter.snopyta.org")
1308 ("\\(?:\\(?:old\\.\\)?reddit\\.com\\)" . "libreddit.de")
1309 ("medium\\.com" . "scribe.rip")
1310 ;; Text-mode of non-text-mode sites
1311 ("www\\.npr\\.org" . "text.npr.org")
1312 ;; Ask for raw versions of paste sites
1313 ("^.*dpaste\\.com.*$" . "\\&.txt")
1314 ("bpa\\.st/\\(.*\\)" . "bpa.st/raw/\\1")
1315 ("\\(paste\\.debian\\.net\\)/\\(.*\\)" . "\\1/plain/\\2")
1316 ("\\(pastebin\\.com\\)/\\\(.*\\)" . "\\1/raw/\\2")
1317 ("\\(paste\\.centos\\.org/view\\)/\\(.*\\)" . "\\1/raw/\\2")))
1318 (browse-url-transform-mode)
1319 (hide-minor-mode 'browse-url-transform-mode))
1320
1321(with-eval-after-load 'browse-url-transform
1322 (setopt eww-url-transformers
1323 '(eww-remove-tracking
1324 browse-url-transform-url)))
1325
1208;; External browsers: firefox > chromium > chrome 1326;; External browsers: firefox > chromium > chrome
1209(setq browse-url-firefox-program 1327(setq browse-url-firefox-program
1210 (or (executable-find "firefox") 1328 (or (executable-find "firefox")
@@ -1226,87 +1344,100 @@ ORG-EXPORT-ARGS are passed to `org-export-to-buffer'."
1226 (browse-url-chrome-program #'browse-url-chrome) 1344 (browse-url-chrome-program #'browse-url-chrome)
1227 (t #'browse-url-default-browser))) 1345 (t #'browse-url-default-browser)))
1228 1346
1229(ensure-package 'link-hint) 1347;; Hinting at links
1230(setopt link-hint-avy-style 'at-full) 1348(when (package-ensure 'link-hint)
1231(setopt link-hint-avy-all-windows t) 1349 (setopt link-hint-avy-style 'at-full)
1232 1350 (setopt link-hint-avy-all-windows t)
1233(defvar link-hint-map 1351 (defvar link-hint-map
1234 (define-keymap 1352 (define-keymap
1235 :name "Open a link" 1353 :name "Open a link"
1236 :prefix 'link-hint-map 1354 :prefix 'link-hint-map
1237 "M-l" #'link-hint-open-link 1355 "M-l" #'link-hint-open-link
1238 "M-w" #'link-hint-copy-link)) 1356 "M-w" #'link-hint-copy-link))
1239 1357 (keymap-global-set "M-l" 'link-hint-map))
1240(keymap-global-set "M-l" 'link-hint-map)
1241
1242(setq global-mode-string
1243 '((jabber-activity-mode jabber-activity-mode-string)
1244 " "))
1245
1246(add-hook 'prog-mode-hook #'prettify-symbols-mode)
1247
1248(require 'autoinsert)
1249(setf (alist-get "\\.scm" auto-insert-alist nil nil #'equal)
1250 '(nil
1251 "#!/bin/sh" \n
1252 "#| -*- scheme -*-" \n
1253 "exec csi -R r7rs -ss \"$0\" \"$@\"" \n
1254 _ \n
1255 "|#" \n \n))
1256
1257(ensure-package 'embark nil t)
1258(when (package-installed-p 'consult)
1259 (ensure-package 'embark-consult nil t))
1260
1261(keymap-global-set "C-." #'embark-act)
1262(keymap-global-set "M-." #'embark-dwim)
1263(keymap-global-set "C-h B" #'embark-bindings)
1264
1265(setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
1266(setopt eldoc-idle-delay 0.01)
1267
1268(setf (alist-get "\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
1269 display-buffer-alist
1270 nil nil #'equal)
1271 '(nil (window-parameters (mode-line-format . none))))
1272
1273(add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode)
1274
1275(global-goto-address-mode)
1276 1358
1277(ensure-package 'pulse-location t t) 1359
1278(pulse-location-mode) 1360;;; Eshell
1279(hide-minor 'pulse-location-mode) 1361
1280(setopt pulse-flag t) 1362(setopt eshell-modules-list
1281 1363 '(eshell-alias
1282(define-advice eval-region (:around (orig start end &rest args) pulse) 1364 eshell-banner
1283 (apply orig start end args) 1365 eshell-basic
1284 (pulse-momentary-highlight-region start end)) 1366 eshell-cmpl
1367 eshell-dirs
1368 eshell-elecslash
1369 eshell-extpipe
1370 eshell-glob
1371 eshell-hist
1372 eshell-ls
1373 eshell-pred
1374 eshell-prompt
1375 eshell-script
1376 eshell-unix))
1377
1378(setopt eshell-banner-message "")
1379(setopt eshell-destroy-buffer-when-process-dies t)
1380(setopt eshell-error-if-no-glob t)
1381(setopt eshell-hist-ignoredups 'erase)
1382(setopt eshell-kill-on-exit t)
1383(setopt eshell-prefer-lisp-functions t)
1384(setopt eshell-prefer-lisp-variables t)
1385(setopt eshell-scroll-to-bottom-on-input 'this)
1386(setopt eshell-history-size 1024)
1387(setopt eshell-input-filter (lambda (input)
1388 (or (eshell-input-filter-default input)
1389 (eshell-input-filter-initial-space input))))
1390(setopt eshell-prompt-function
1391 (lambda ()
1392 (concat (if (= 0 eshell-last-command-status)
1393 "^_^"
1394 ";_;")
1395 " "
1396 (abbreviate-file-name (eshell/pwd))
1397 (if (= (user-uid) 0)
1398 " # "
1399 " $ "))))
1400
1401(add-hook 'eshell-mode-hook
1402 (defun eshell-setup ()
1403 (setq-local outline-regexp eshell-prompt-regexp)
1404 (setq-local page-delimiter eshell-prompt-regexp)
1405 (setq-local imenu-generic-expression
1406 '(("Prompt" " \\($\\|#\\) \\(.*\\)" 2)))
1407 (setq-local truncate-lines t)))
1408
1409(setenv "PAGER" (executable-find "cat"))
1410
1411(defun eshellp (buffer-or-name)
1412 (with-current-buffer buffer-or-name
1413 (derived-mode-p 'eshell-mode)))
1414
1415(defun eshell-pop-up (&optional arg)
1416 "Pop up an eshell in the `default-directory'.
1417NEW is passed to `eshell'."
1418 (interactive "P")
1419 (require 'eshell)
1420 (let ((dir default-directory)
1421 (display-comint-buffer-action 'pop-to-buffer))
1422 (if-let ((buf (and (not arg)
1423 (or (get-buffer eshell-buffer-name)
1424 (seq-find #'eshellp (reverse (buffer-list)))))))
1425 (pop-to-buffer buf)
1426 (eshell arg))
1427 ;; In the eshell buffer
1428 (unless (equal default-directory dir)
1429 (eshell/cd dir)
1430 (eshell-send-input)
1431 (goto-char (point-max)))))
1432
1433(keymap-global-set "C-z" #'eshell-pop-up)
1434(with-eval-after-load 'esh-mode
1435 (keymap-set eshell-mode-map "C-z" #'quit-window))
1436
1437(when (package-ensure 'eat)
1438 (add-hook 'eshell-first-time-mode-hook #'eat-eshell-mode))
1439
1440(when (package-ensure 'wiki-abbrev t)
1441 (wiki-abbrev-insinuate)
1442 (add-hook 'text-mode-hook #'abbrev-mode))
1285(put 'list-timers 'disabled nil) 1443(put 'list-timers 'disabled nil)
1286
1287(defun string-intersperse (strings delim)
1288 "Intersperse STRINGS with DELIM and return the concatenated result."
1289 (cl-loop for string in strings
1290 concat (concat delim string) into acc
1291 finally return (substring acc (length delim))))
1292
1293(add-to-list 'inhibit-message-regexps
1294 (string-intersperse (map 'list #'expand-file-name
1295 (list recentf-save-file
1296 save-place-file))
1297 "\\|"))
1298
1299(add-to-list 'set-message-functions 'inhibit-message)
1300
1301(ensure-package 'web-mode)
1302(add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
1303(add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
1304(add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
1305(add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
1306(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
1307(add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
1308(add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))
1309(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
1310
1311(add-hook 'help-fns-describe-function-functions
1312 #'shortdoc-help-fns-examples-function)