diff options
-rw-r--r-- | emacs.el | 1931 |
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. |
16 | If LOCAL is t, add ~/src/PKG.el to `load-path' and generate autoloads. | ||
17 | If 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")))) | 40 | Written as a convenience to writing out this long `alist-get' |
41 | call every time. If VAL is nil, the entry with KEY is removed from ALIST unless | ||
42 | KEEP 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) |
33 | MESSAGE 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) |
45 | If 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 | |
47 | basically." | 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) |
108 | When 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) |
121 | Will not replace an existing template unless `:force' in ARGS is | 130 | (if (and (executable-find "darkman") |
122 | non-nil. ARGS is a plist, which in addition to the additional | 131 | (let ((stat (shell-command "darkman get"))) |
123 | options `org-capture-templates' accepts (which see), also accepts | 132 | (and (= stat 0) |
124 | the 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. | ||
155 | If a window is the only buffer on a frame, switch buffer. When | ||
156 | run 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. | ||
164 | If the current window is the only window in the frame, bury its | ||
165 | buffer 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'. | ||
173 | That is, with a positive N, deletes newlines as well, leaving -N | ||
174 | spaces. If N is negative, it will not delete newlines and leave N | ||
175 | spaces." | ||
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. | ||
247 | Good 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. | ||
277 | With 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. | ||
326 | If LOCAL is t, add ~/src/PKG.el to `load-path'. | ||
327 | If LOCAL is a string, add that directory to the `load-path'. | ||
328 | If 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. | ||
423 | If called with ARG non-nil, or with \\[universal-argument], | ||
424 | disconnect 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. | ||
221 | HOOK 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. | ||
559 | HOOK 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. | ||
443 | Good 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. | ||
779 | Calls `other-window', which see, unless | ||
780 | - the current window is alone on its frame | ||
781 | - `other-window-dwim' is called with \\[universal-argument] | ||
782 | In 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) | 791 | If the current window is alone in its frame, bury the buffer |
792 | instead." | ||
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. | |||
1019 | Defaults to 'wl-copy' on wayland and 'xclip' on Xorg." | 968 | Defaults 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. |
1038 | ORG-EXPORT-ARGS are passed to `org-export-to-buffer'." | 973 | ORG-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) | 1017 | Will not replace an existing template unless `:force' in ARGS is |
1018 | non-nil. ARGS is a plist, which in addition to the additional | ||
1019 | options `org-capture-templates' accepts (which see), also accepts | ||
1020 | the 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. | ||
1205 | If called with ARG non-nil, or with \\[universal-argument], | ||
1206 | disconnect 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'. | ||
1417 | NEW 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) | ||