about summary refs log tree commit diff stats
path: root/emacs.el
diff options
context:
space:
mode:
Diffstat (limited to 'emacs.el')
-rw-r--r--emacs.el1029
1 files changed, 1029 insertions, 0 deletions
diff --git a/emacs.el b/emacs.el new file mode 100644 index 0000000..c6d361b --- /dev/null +++ b/emacs.el
@@ -0,0 +1,1029 @@
1;;; ~/.emacs -*- mode: emacs-lisp; lexical-binding: t; -*-
2;; by Case Duckworth <acdw@acdw.net>
3;; Bankruptcy 10: "Annoyance"
4
5;;; Commentary:
6
7;; This is my Emacs configuration. There are many like it but this
8;; one is mine.
9;;
10;; For the tenth time!
11
12;;; Code:
13
14(add-hook 'after-init-hook
15 (lambda ()
16 (load (locate-user-emacs-file "private"))))
17
18;;; Definitions:
19
20(defun reset-faces ()
21 (dolist (face '(font-lock-regexp-face
22 font-lock-builtin-face
23 font-lock-variable-name-face
24 font-lock-preprocessor-face
25 font-lock-remove-face
26 font-lock-delimiter-face
27 font-lock-label-face
28 font-lock-operator-face
29 font-lock-property-face
30 font-lock-builtin-face
31 font-lock-number-face
32 font-lock-keyword-face
33 font-lock-set-face
34 font-lock-warning-face
35 font-lock-punctuation-face
36 font-lock-constant-face
37 font-lock-type-face
38 font-lock-function-name-face
39 font-lock-reference-face
40 font-lock-misc-punctuation-face
41 font-lock-bracket-face))
42 (face-spec-set face '((t :foreground unspecified
43 :background unspecified)))))
44
45(defun electric-pair-local-mode-disable ()
46 "Disable `electric-pair-mode', locally."
47 (electric-pair-local-mode -1))
48
49(defun kill-this-buffer (&optional buffer-or-name)
50 "Kill this buffer, or BUFFER-OR-NAME.
51When called interactvely, the user will be prompted when passing
52\\[universal-argument]."
53 (interactive "P")
54 (cond
55 ((bufferp buffer-or-name)
56 (kill-buffer buffer-or-name))
57 ((null buffer-or-name)
58 (kill-current-buffer))
59 (:else
60 (kill-buffer (read-buffer "Kill: " nil :require-match)))))
61
62(defun define-org-capture-template (description &rest args)
63 "Define an template for `org-capture-templates'.
64Will not replace an existing template unless `:force' in ARGS is
65non-nil. ARGS is a plist, which in addition to the additional
66options `org-capture-templates' accepts (which see), also accepts
67the following: `:keys', `:description', `:type', `:target', and
68`:template'."
69 (declare (indent 1))
70 (let* ((keys (plist-get args :keys))
71 (type (plist-get args :type))
72 (target (plist-get args :target))
73 (template (plist-get args :template))
74 (force (plist-get args :force))
75 (template-value
76 (append
77 (list description)
78 (when (or type target template)
79 (list (or type 'entry) target template))
80 (cl-loop for i from 0 below (length args) by 2
81 unless (member (nth i args)
82 '( :keys :description :type
83 :target :template))
84 append (list (nth i args)
85 (plist-get args (nth i args)))))))
86 (if (seq-find (lambda (el) (equal (car el) keys))
87 org-capture-templates)
88 (and force
89 (setf (alist-get keys org-capture-templates nil nil #'equal)
90 template-value))
91 (setf org-capture-templates
92 (append org-capture-templates
93 (list (cons keys template-value)))))
94 org-capture-templates))
95
96(defun other-window-or-switch-buffer (&optional arg)
97 "Switch to the other window.
98If a window is the only buffer on a frame, switch buffer. When
99run with \\[universal-argument], unconditionally switch buffer."
100 (interactive "P")
101 (if (or arg (one-window-p))
102 (switch-to-buffer (other-buffer) nil t)
103 (other-window 1)))
104
105(defun cycle-spacing@ (&optional n)
106 ;; `cycle-spacing' is wildly different in 29.1 over 28.
107 "Negate N argument on `cycle-spacing'.
108That is, with a positive N, deletes newlines as well, leaving -N
109spaces. If N is negative, it will not delete newlines and leave
110N spaces."
111 (interactive "*p")
112 (cycle-spacing (- n)))
113
114(defun first-frame@set-fonts ()
115 (remove-hook 'server-after-make-frame-hook
116 #'first-frame@set-fonts)
117 (face-spec-set 'default
118 `((t :family "Recursive Mono Casual Static"
119 :height 110)))
120 (face-spec-set 'variable-pitch
121 `((t :family "Recursive Sans Casual Static"
122 :height 1.0)))
123 ;; Emojis
124 (cl-loop with ffl = (font-family-list)
125 for font in '("Noto Emoji" "Noto Color Emoji"
126 "Segoe UI Emoji" "Apple Color Emoji"
127 "FreeSans" "FreeMono" "FreeSerif"
128 "Unifont" "Symbola")
129 if (member font ffl)
130 do (set-fontset-font t 'symbol font))
131 ;; International fonts
132 (cl-loop with ffl = (font-family-list)
133 for (charset . font)
134 in '((latin . "Noto Sans")
135 (han . "Noto Sans CJK SC Regular")
136 (kana . "Noto Sans CJK JP Regular")
137 (hangul . "Noto Sans CJK KR Regular")
138 (cjk-misc . "Noto Sans CJK KR Regular")
139 (khmer . "Noto Sans Khmer")
140 (lao . "Noto Sans Lao")
141 (burmese . "Noto Sans Myanmar")
142 (thai . "Noto Sans Thai")
143 (ethiopic . "Noto Sans Ethiopic")
144 (hebrew . "Noto Sans Hebrew")
145 (arabic . "Noto Sans Arabic")
146 (gujarati . "Noto Sans Gujarati")
147 (devanagari . "Noto Sans Devanagari")
148 (kannada . "Noto Sans Kannada")
149 (malayalam . "Noto Sans Malayalam")
150 (oriya . "Noto Sans Oriya")
151 (sinhala . "Noto Sans Sinhala")
152 (tamil . "Noto Sans Tamil")
153 (telugu . "Noto Sans Telugu")
154 (tibetan . "Noto Sans Tibetan"))
155 if (member font ffl)
156 do (set-fontset-font t charset font))
157 ;; XXX: tab-bar does a weird thing, so i set it up here....
158 (setopt tab-bar-show t)
159 (tab-bar-mode))
160
161(defun renz/sort-by-alpha-length (elems)
162 "Sort ELEMS first alphabetically, then by length."
163 (sort elems (lambda (c1 c2)
164 (or (string-version-lessp c1 c2)
165 (< (length c1) (length c2))))))
166
167(defun renz/sort-by-history (elems)
168 "Sort ELEMS by minibuffer history.
169Use `mct-sort-sort-by-alpha-length' if no history is available."
170 (if-let ((hist (and (not (eq minibuffer-history-variable t))
171 (symbol-value minibuffer-history-variable))))
172 (minibuffer--sort-by-position hist elems)
173 (renz/sort-by-alpha-length elems)))
174
175(defun renz/completion-category ()
176 "Return completion category."
177 (when-let ((window (active-minibuffer-window)))
178 (with-current-buffer (window-buffer window)
179 (completion-metadata-get
180 (completion-metadata (buffer-substring-no-properties
181 (minibuffer-prompt-end)
182 (max (minibuffer-prompt-end) (point)))
183 minibuffer-completion-table
184 minibuffer-completion-predicate)
185 'category))))
186
187(defun renz/sort-multi-category (elems)
188 "Sort ELEMS per completion category."
189 (pcase (renz/completion-category)
190 ('nil elems) ; no sorting
191 ('kill-ring elems)
192 ('project-file (renz/sort-by-alpha-length elems))
193 (_ (renz/sort-by-history elems))))
194
195(defvar no-tabs-modes '(emacs-lisp-mode
196 lisp-mode
197 scheme-mode
198 python-mode
199 haskell-mode)
200 "Modes /not/ to indent with tabs.")
201
202(defun indent-tabs-mode-maybe ()
203 (if (apply #'derived-mode-p no-tabs-modes)
204 (indent-tabs-mode -1)
205 (indent-tabs-mode 1)))
206
207(define-minor-mode truncate-lines-mode
208 "Buffer-local mode to toggle `truncate-lines'."
209 :lighter ""
210 (setq-local truncate-lines truncate-lines-mode))
211
212;;; Region or buffer stuff
213
214(defun call-with-region-or-buffer (fn &rest _r)
215 "Call function FN with current region or buffer.
216Good to use for :around advice."
217 (if (region-active-p)
218 (funcall fn (region-beginning) (region-end))
219 (funcall fn (point-min) (point-max))))
220
221(defun delete-trailing-whitespace-except-current-line ()
222 (save-excursion
223 (delete-trailing-whitespace (point-min)
224 (line-beginning-position))
225 (delete-trailing-whitespace (line-end-position)
226 (point-max))))
227
228(defun create-missing-directories ()
229 "Automatically create missing directories."
230 (let ((target-dir (file-name-directory buffer-file-name)))
231 (unless (file-exists-p target-dir)
232 (make-directory target-dir :parents))))
233
234
235(defun vc-remote-off ()
236 "Turn VC off when remote."
237 (when (file-remote-p (buffer-file-name))
238 (setq-local vc-handled-backends nil)))
239
240(defun +titlecase-sentence-style-dwim (&optional arg)
241 "Titlecase a sentence.
242With prefix ARG, toggle the value of
243`titlecase-downcase-sentences' before sentence-casing."
244 (interactive "P")
245 (let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences)
246 titlecase-downcase-sentences)))
247 (titlecase-dwim 'sentence)))
248
249(defun +titlecase-org-headings ()
250 (interactive)
251 (require 'org)
252 (save-excursion
253 (goto-char (point-min))
254 ;; See also `org-map-tree'. I'm not using that function because I want to
255 ;; skip the first headline. A better solution would be to patch
256 ;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc).
257 (let ((level (funcall outline-level))
258 (org-special-ctrl-a/e t))
259 (while (and (progn (outline-next-heading)
260 (> (funcall outline-level) level))
261 (not (eobp)))
262 (titlecase-region (progn (org-beginning-of-line) (point))
263 (progn (org-end-of-line) (point)))))))
264
265(defcustom browse-url-safe-browser-functions nil
266 "\"Safe\" browser functions."
267 :type '(repeat-function))
268
269(defun browse-url-browser-function-safe-p (fn)
270 "Return t if FN is a \"safe\" browser function."
271 (memq f (append browse-url-safe-browser-functions
272 (mapcar (lambda (i)
273 (plist-get (cdr i) :value))
274 (seq-filter (lambda (i)
275 (eq (car i) 'function-item))
276 (cdr (get 'browse-url-browser-function
277 'custom-type)))))))
278
279(put 'browse-url-browser-function 'safe-local-variable
280 'browse-url-browser-function-safe-p)
281
282
283;;; Packages:
284
285(require 'package)
286(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
287(package-initialize)
288
289(defun ensure-package (pkg &optional localp)
290 "Esnure PKG is installed from repositories.
291If LOCALP is t, add ~/src/PKG.el to `load-path'.
292If LOCALP is a string, add that directory to the `load-path'."
293 (cond
294 ((stringp localp)
295 (and (file-exists-p localp)
296 (add-to-list 'load-path localp)))
297 (localp
298 (ensure-package pkg
299 (expand-file-name
300 (format "~/src/%s.el"
301 (symbol-name pkg)))))
302 (:else
303 (unless (package-installed-p pkg)
304 (unless (ignore-errors (package-install pkg))
305 (package-refresh-contents)
306 (package-install pkg))))))
307
308;; Install packages here. Acutal configuration is done in the Configuration
309;; section.
310(ensure-package 'consult)
311(ensure-package 'marginalia)
312(ensure-package 'visual-fill-column)
313(ensure-package 'adaptive-wrap)
314(ensure-package 'geiser)
315(when (executable-find "csi")
316 (ensure-package 'geiser-chicken))
317(ensure-package 'avy)
318(ensure-package 'zzz-to-char)
319(ensure-package 'hungry-delete)
320(ensure-package 'undohist)
321(ensure-package 'jinx)
322(ensure-package 'markdown-mode)
323(ensure-package 'anzu)
324
325;; Local packages
326(ensure-package 'scule t)
327(ensure-package 'frowny t)
328(ensure-package 'hippie-completing-read t)
329(ensure-package 'mode-line-bell t)
330(ensure-package 'titlecase t)
331(ensure-package 'jabber t)
332
333;;; Jabber
334
335(use-package jabber
336 :load-path "~/src/jabber.el"
337 :defer t
338 :bind-keymap (("C-c j" . jabber-global-keymap))
339 :preface nil
340 (setq-default jabber-chat-buffer-format "*%n*"
341 jabber-browse-buffer-format "*%n*"
342 jabber-groupchat-buffer-format "*%n*"
343 jabber-muc-private-buffer-format "*%n*")
344 :custom-face
345 (jabber-activity-face ((t :inherit jabber-chat-prompt-foreign
346 :foreground unspecified
347 :weight normal)))
348 (jabber-activity-personal-face ((t :inherit jabber-chat-prompt-local
349 :foreground unspecified
350 :weight bold)))
351 (jabber-chat-prompt-local ((t :inherit minibuffer-prompt
352 :foreground unspecified
353 :weight normal
354 :slant italic)))
355 (jabber-chat-prompt-foreign ((t :inherit warning
356 :foreground unspecified
357 :weight normal)))
358 (jabber-chat-prompt-system ((t :inherit font-lock-doc-face
359 :foreground unspecified)))
360 (jabber-rare-time-face ((t :inherit font-lock-comment-face
361 :foreground unspecified
362 :underline nil)))
363 :config
364 (require 'jabber-httpupload nil t)
365 (setopt jabber-auto-reconnect t
366 jabber-last-read-marker "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~"
367 jabber-muc-decorate-presence-patterns
368 '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil)
369 ("Mode #.*" . jabber-muc-presence-dim)
370 ("." . jabber-muc-presence-dim))
371 jabber-activity-make-strings #'jabber-activity-make-strings-shorten
372 jabber-rare-time-format
373 (format " - - - - - %%H:%d %%F"
374 (let ((min (string-to-number (format-time-string "%M"))))
375 (* 5 (floor min 5))))
376 jabber-muc-header-line-format '(" " jabber-muc-topic))
377
378 (setopt jabber-groupchat-prompt-format "%n. "
379 jabber-chat-local-prompt-format "%n. "
380 jabber-chat-foreign-prompt-format "%n. "
381 jabber-muc-private-foreign-prompt-format "%g/%n. ")
382
383 (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to)
384 (map-keymap (lambda (key command)
385 (define-key jabber-global-keymap (vector (+ key #x60)) command))
386 jabber-global-keymap)
387 (keymap-global-set "C-x C-j" #'dired-jump)
388
389 (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons)
390 (remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo)
391 (remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo)
392 (add-hook 'jabber-chat-mode-hook 'visual-line-mode)
393 (add-hook 'jabber-chat-mode-hook (defun jabber-no-position ()
394 (setq-local mode-line-position nil)))
395
396 (add-hook 'jabber-alert-muc-hooks
397 (defun jabber@highlight-acdw (&optional _ _ buf _ _)
398 (when buf
399 (with-current-buffer buf
400 (let ((regexp (rx word-boundary
401 "acdw" ; maybe get from the config?
402 word-boundary)))
403 (hi-lock-unface-buffer regexp)
404 (highlight-regexp regexp 'jabber-chat-prompt-local))))))
405
406 (when (fboundp 'jabber-chat-update-focus)
407 (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus)))
408
409
410;;; Configuration:
411
412(setopt custom-file (locate-user-emacs-file "custom.el"))
413(load custom-file :noerror)
414
415;;; General keybinding changes
416
417(keymap-global-set "M-o" #'other-window-or-switch-buffer)
418(keymap-global-set "M-SPC" #'cycle-spacing@)
419(keymap-global-set "M-u" #'universal-argument)
420(keymap-set universal-argument-map "M-u" #'universal-argument-more)
421
422;;; Theme
423
424(if (daemonp)
425 (add-hook 'server-after-make-frame-hook #'first-frame@set-fonts)
426 (run-with-idle-timer 1 nil #'first-frame@set-fonts))
427
428(tool-bar-mode -1)
429
430(setopt modus-themes2-bold-constructs nil
431 modus-themes-italic-constructs t
432 modus-themes-variable-pitch-ui t)
433
434(add-hook 'modus-themes-after-load-theme-hook #'reset-faces)
435
436(load-theme 'modus-vivendi :no-confirm :no-enable)
437(load-theme 'modus-operandi :no-confirm)
438
439(add-hook 'text-mode-hook #'visual-line-mode)
440(add-hook 'prog-mode-hook #'auto-fill-mode)
441(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode)
442
443;;; Mode line
444
445(defvar mode-line-position
446 '(""
447 (:propertize
448 (""
449 (:eval (if line-number-mode "%3l" ""))
450 (:eval (if column-number-mode
451 (if column-number-indicator-zero-based
452 "/%2c"
453 "/%2C")
454 "")))
455 display (min-width (3.0)))
456 (:propertize (" [" (-3 "%p") "] ")
457 display (min-width (6.0)))))
458
459(setopt mode-line-format
460 '(("%e"
461 mode-line-front-space
462 (:propertize (""
463 mode-line-client
464 mode-line-modified
465 mode-line-remote)
466 display (min-width (3.0)))
467 " "
468 mode-line-buffer-identification
469 (vc-mode (" (" (:eval (string-trim vc-mode)) ")"))
470 " "
471 (mode-line-position mode-line-position)
472 mode-line-modes
473 mode-line-misc-info
474 mode-line-end-spaces)))
475
476;; Remove modes from mode-line
477(dolist (minor-mode '(frowny-mode
478 whitespace-mode
479 hungry-delete-mode))
480 (setf (alist-get minor-mode minor-mode-alist) (list ""))
481 (add-hook (intern (format "%s-hook" minor-mode))
482 (lambda ()
483 (setf (alist-get minor-mode minor-mode-alist) (list "")))))
484
485;;; Completion & minibuffer
486
487(fido-vertical-mode)
488(minibuffer-depth-indicate-mode)
489
490(setopt completion-auto-help (not icomplete-mode)
491 completion-auto-select 'second-tab
492 completions-header-format nil
493 completions-max-height 12
494 completions-format 'one-column
495 completion-styles '(basic partial-completion emacs22 flex)
496 completion-ignore-case t
497 read-buffer-completion-ignore-case t
498 read-file-name-completion-ignore-case t
499 completions-detailed t
500 enable-recursive-minibuffers t
501 file-name-shadow-properties '(invisible t intangible t)
502 minibuffer-eldef-shorten-default t
503 minibuffer-prompt-properties '( read-only t
504 cursor-intangible t
505 face minibuffer-prompt)
506 window-resize-pixelwise t
507 frame-resize-pixelwise t)
508
509(add-hook 'completion-list-mode-hook #'truncate-lines-mode)
510(add-hook 'minibuffer-setup-hook #'truncate-lines-mode)
511
512;; Up/down when completing in the minibuffer
513;; (define-key minibuffer-local-map (kbd "C-p") #'minibuffer-previous-completion)
514;; (define-key minibuffer-local-map (kbd "C-n") #'minibuffer-next-completion)
515
516;; Up/down when competing in a normal buffer
517;; (define-key completion-in-region-mode-map (kbd "C-p") #'minibuffer-previous-completion)
518;; (define-key completion-in-region-mode-map (kbd "C-n") #'minibuffer-next-completion)
519
520(setopt completions-sort #'renz/sort-multi-category)
521
522(setopt tab-always-indent 'complete)
523
524(file-name-shadow-mode)
525(minibuffer-electric-default-mode)
526
527(scroll-bar-mode -1)
528(menu-bar-mode -1)
529
530(add-hook 'prog-mode-hook #'indent-tabs-mode-maybe)
531
532(setopt electric-pair-skip-whitespace 'chomp)
533(electric-pair-mode)
534
535(setopt sh-basic-offset tab-width)
536
537(keymap-set emacs-lisp-mode-map "C-c C-c" #'eval-defun)
538(keymap-set emacs-lisp-mode-map "C-c C-k" #'eval-buffer)
539(keymap-set lisp-interaction-mode-map "C-c C-c" #'eval-defun)
540(keymap-set lisp-interaction-mode-map "C-c C-k" #'eval-buffer)
541
542(advice-add 'indent-region :around #'call-with-region-or-buffer)
543(advice-add 'tabify :around #'call-with-region-or-buffer)
544(advice-add 'untabify :around #'call-with-region-or-buffer)
545
546(with-eval-after-load 'scheme
547 (keymap-unset scheme-mode-map "M-o" t)
548 ;; Comparse "keywords" --- CHICKEN (http://wiki.call-cc.org/eggref/5/comparse)
549 (put 'sequence* 'scheme-indent-function 1)
550 (put 'satisfies 'scheme-indent-function 1)
551 (add-hook 'scheme-mode-hook #'geiser-mode))
552(with-eval-after-load 'geiser-mode
553 (keymap-set geiser-mode-map "C-c C-k" #'geiser-eval-buffer-and-go)
554 (keymap-unset geiser-mode-map "C-." t))
555
556
557(with-eval-after-load 'visual-fill-column
558 (setopt visual-fill-column-center-text t
559 visual-fill-column-width (+ fill-column 2))
560 (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust))
561(add-hook 'visual-line-mode-hook #'visual-fill-column-mode)
562(add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode)
563
564(setopt major-mode
565 (lambda () ; guess major mode from buffer name
566 (unless buffer-file-name
567 (let ((buffer-file-name (buffer-name)))
568 (set-auto-mode)))))
569
570;; Dialogs
571(unless (boundp 'use-short-answers)
572 (fset 'yes-or-no-p 'y-or-n-p))
573
574(setopt read-answer-short t
575 use-dialog-box nil
576 use-file-dialog nil
577 use-short-answers t)
578
579(require 'savehist)
580(setopt history-length 1024
581 history-delete-duplicates t
582 ;; savehist-file (etc/ "savehist.el")
583 savehist-save-minibuffer-history t
584 savehist-autosave-interval 30)
585(savehist-mode)
586
587;; Killing and yanking
588(setopt kill-do-not-save-duplicates t
589 kill-read-only-ok t
590 ;; XXX: This setting causes an error message the first time it's
591 ;; called: "Selection owner couldn't convert: TIMESTAMP". I have
592 ;; absolutely no idea why I get this error, but it's generated in
593 ;; `x_get_foreign_selection'. I also can't inhibit the message or
594 ;; do anything else with it, so for now, I'll just live with the
595 ;; message.
596 save-interprogram-paste-before-kill t
597 yank-pop-change-selection t)
598(delete-selection-mode)
599
600;; Notifying the user
601(setopt echo-keystrokes 0.01
602 ring-bell-function #'ignore)
603
604;; Point and mark
605(setopt set-mark-command-repeat-pop t)
606
607;; The system
608(setopt read-process-output-max (* 10 1024 1024))
609
610;; Startup
611(setopt inhibit-startup-screen t
612 initial-buffer-choice t
613 initial-scratch-message nil)
614
615(define-advice startup-echo-area-message (:override ())
616 (if (get-buffer "*Warnings*")
617 ";_;"
618 "^_^"))
619
620;; Text editing
621(setopt fill-column 80
622 sentence-end-double-space nil
623 tab-width 8
624 tab-always-indent 'complete)
625(global-so-long-mode)
626
627(setopt show-paren-delay 0.01
628 show-paren-style 'parenthesis
629 show-paren-when-point-in-periphery t
630 show-paren-when-point-inside-paren t)
631(show-paren-mode)
632
633
634;; Encodings
635(set-language-environment "UTF-8")
636(setopt buffer-file-coding-system 'utf-8-unix
637 coding-system-for-read 'utf-8-unix
638 coding-system-for-write 'utf-8-unix
639 default-process-coding-system '(utf-8-unix . utf-8-unix)
640 locale-coding-system 'utf-8-unix)
641(set-charset-priority 'unicode)
642(prefer-coding-system 'utf-8-unix)
643(set-default-coding-systems 'utf-8-unix)
644(set-terminal-coding-system 'utf-8-unix)
645(set-keyboard-coding-system 'utf-8-unix)
646(pcase system-type
647 ((or 'ms-dos 'windows-nt)
648 (set-clipboard-coding-system 'utf-16-le)
649 (set-selection-coding-system 'utf-16-le))
650 (_
651 (set-selection-coding-system 'utf-8)
652 (set-clipboard-coding-system 'utf-8)))
653
654
655;; Files
656(setopt auto-revert-verbose nil
657 global-auto-revert-non-file-buffers t
658 create-lockfiles nil
659 find-file-visit-truename t
660 mode-require-final-newline t
661 view-read-only t
662 save-silently t)
663(global-auto-revert-mode)
664
665(setopt auto-save-default nil
666 auto-save-interval 1
667 auto-save-no-message t
668 auto-save-timeout 1
669 auto-save-visited-interval 1
670 remote-file-name-inhibit-auto-save-visited t)
671(add-to-list 'auto-save-file-name-transforms
672 `(".*" ,(locate-user-emacs-file "auto-save/") t))
673(auto-save-visited-mode)
674
675(setopt backup-by-copying t
676 version-control t
677 kept-new-versions 8
678 kept-old-versions 8
679 delete-old-versions t)
680(setq-default backup-directory-alist
681 `(("^/dev/shm" . nil)
682 ("^/tmp" . nil)
683 (,(getenv "XDG_RUNTIME_DIR") . nil)
684 ("." . ,(locate-user-emacs-file "backup"))))
685
686(require 'recentf)
687(setopt
688 recentf-max-menu-items 500
689 recentf-max-saved-items nil ; Save the whole list
690 recentf-auto-cleanup 'mode
691 recentf-case-fold-search t)
692;; (add-to-list 'recentf-exclude etc/)
693(add-to-list 'recentf-exclude "-autoloads.el\\'")
694(add-hook 'buffer-list-update-hook #'recentf-track-opened-file)
695(add-hook 'after-save-hook #'recentf-save-list)
696(recentf-mode)
697
698(require 'saveplace)
699(setopt
700 save-place-forget-unreadable-files (eq system-type
701 'gnu/linux))
702(save-place-mode)
703
704(require 'uniquify)
705(setq uniquify-after-kill-buffer-p t
706 uniquify-buffer-name-style 'forward
707 uniquify-ignore-buffers-re "^\\*"
708 uniquify-separator path-separator)
709
710(setq-local vc-follow-symlinks t
711 vc-make-backup-files t)
712
713;; Whitespace
714(require 'whitespace)
715(setopt whitespace-style
716 '(face trailing tabs tab-mark))
717(global-whitespace-mode)
718(add-hook 'before-save-hook #'delete-trailing-whitespace-except-current-line)
719
720;; Native compilation
721(setopt native-comp-async-report-warnings-errors 'silent
722 native-comp-deferred-compilation t
723 native-compile-target-directory
724 (locate-user-emacs-file "eln"))
725(when (boundp 'native-comp-eln-load-path)
726 (add-to-list 'native-comp-eln-load-path native-compile-target-directory))
727(when (fboundp 'startup-redirect-eln-cache)
728 (startup-redirect-eln-cache native-compile-target-directory))
729
730(global-goto-address-mode)
731
732;; Winner
733(winner-mode)
734
735;;; Hooks
736(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
737(add-hook 'find-file-not-found-functions #'create-missing-directories)
738(add-hook 'find-file-hook #'vc-remote-off)
739(add-hook 'dired-mode-hook #'hl-line-mode)
740(add-hook 'org-agenda-mode-hook #'hl-line-mode)
741
742;;; Tab bar
743
744(defun tab-bar-end-space ()
745 `((end menu-item " " ignore)))
746
747(add-to-list 'tab-bar-format 'tab-bar-format-align-right :append)
748(add-to-list 'tab-bar-format 'tab-bar-format-global :append)
749(add-to-list 'tab-bar-format 'tab-bar-end-space :append)
750;;(setopt tab-bar-show t)
751;;(tab-bar-mode) ; done after setting fonts
752
753;;; Org mode
754
755(keymap-global-set "C-c a" #'org-agenda)
756(keymap-global-set "C-c c" #'org-capture)
757(setopt org-clock-clocked-in-display 'frame-title
758 org-clock-frame-title-format
759 '("%b" " - " (t org-mode-line-string))
760 org-tags-column (- (- fill-column 3))
761 org-log-into-drawer t
762 org-clock-into-drawer t)
763
764;;; Spelling
765
766(defun list-of-strings-p (x)
767 "Is X a list of strings?"
768 (and x
769 (listp x)
770 (cl-every #'stringp x)))
771
772(put 'ispell-local-words 'safe-local-variable
773 'list-of-strings-p)
774
775(add-hook 'text-mode-hook #'jinx-mode)
776(with-eval-after-load 'jinx
777 (keymap-set jinx-mode-map "M-$" #'jinx-correct)
778 (keymap-set jinx-mode-map "C-M-$" #'jinx-languages))
779
780;;; org-return-dwim
781;; https://github.com/alphapapa/unpackaged.el,
782;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/
783(defun org-return-dwim (&optional arg)
784 "A helpful replacement for `org-return'.
785When called interactively with \\[universal-argument], call `org-return'
786itself. Other values of ARG will call `newline' with that ARG."
787 (interactive "P")
788 ;; Auto-fill if enabled
789 (when auto-fill-function
790 (dolist (func (ensure-list auto-fill-function))
791 (funcall func)))
792 (cl-letf* ((el (org-element-at-point))
793 ((symbol-function 'el-child-of)
794 (lambda (&rest types)
795 (org-element-lineage el types t))))
796 (cond ; Figure out what we're going to do
797 (arg ; Handle prefix ARG
798 (pcase arg
799 ('(4) (org-return t nil t))
800 (_ (newline arg t))))
801 ((and org-return-follows-link ; Open a link
802 (el-child-of 'link))
803 (org-open-at-point-global))
804 ((org-at-heading-p) ; Open a paragraph after a heading
805 (let ((heading-start (org-entry-beginning-position)))
806 (goto-char (org-entry-end-position))
807 (cond ((and (org-at-heading-p) ; Entry is only a heading
808 (= heading-start (org-entry-beginning-position)))
809 (end-of-line)
810 (newline 2))
811 (:else ; Entry is more than a heading
812 (forward-line -1)
813 (end-of-line)
814 (when (org-at-heading-p)
815 ;; Open a paragraph
816 (forward-line)
817 (newline)
818 (forward-line -1))
819 (while (not (looking-back "\\(?:[[:blank:]]?\n\\)\\{3\\}" nil))
820 (newline))
821 (forward-line -1)))))
822 ((org-at-item-checkbox-p) ; Insert a new checkbox item
823 (end-of-line)
824 (org-insert-todo-heading nil))
825 ((org-in-item-p) ; Insert a new list item
826 (let* ((context (org-element-context el))
827 (first-item-p (eq 'plain-list (car context)))
828 (itemp (eq 'item (car context)))
829 (emptyp (or
830 ;; This (regular) list item is empty
831 (eq (org-element-property :contents-begin context)
832 (org-element-property :contents-end context))
833 ;; This (definition) list item is empty
834 (looking-at " *::")))
835 (item-child-p (el-child-of 'item)))
836 (cond ((and itemp emptyp)
837 ;; This test has to be here even though it's the same as the
838 ;; :else clause, because an item that's empty will also satisfy
839 ;; the next clause.
840 (delete-region (line-beginning-position) (line-end-position))
841 (newline))
842 ((or first-item-p
843 (and itemp (not emptyp))
844 item-child-p)
845 (org-end-of-item)
846 (org-insert-item))
847 (:else
848 (delete-region (line-beginning-position) (line-end-position))
849 (newline)))))
850 ((and (fboundp 'org-inlinetask-in-task-p) ; Just return for inline tasks
851 (org-inlinetask-in-task-p))
852 (org-return))
853 ((org-at-table-p) ; Insert a new table row
854 (cond ((save-excursion ; Empty row: end the table
855 (beginning-of-line)
856 (cl-loop with end = (line-end-position)
857 for cell = (org-element-table-cell-parser)
858 always (eq (org-element-property :contents-begin cell)
859 (org-element-property :contents-end cell))
860 while (re-search-forward "|" end t)))
861 (delete-region (line-beginning-position) (line-end-position))
862 (org-return))
863 (:else ; Non-empty row
864 (org-return))))
865 (:else ; Something else
866 (org-return)))))
867
868(defun org-table-copy-down|org-return-dwim (&optional n)
869 "Call `org-table-copy-down' or `+org-return' depending on context."
870 (interactive "P")
871 (if (org-table-check-inside-data-field 'noerror)
872 (org-table-copy-down (or n 1))
873 (org-return-dwim n)))
874
875(with-eval-after-load 'org
876 (keymap-set org-mode-map "RET" #'org-return-dwim)
877 (keymap-set org-mode-map "S-RET" #'org-table-copy-down|org-return-dwim))
878
879;;; Copy rich text to the keyboard
880
881(defcustom clipboard-html-copy-program
882 (if (or (equal "wayland"
883 (getenv "XDG_SESSION_TYPE"))
884 (getenv "WAYLAND_DISPLAY"))
885 '("wl-copy" "-t" "text/html")
886 '("xclip" "-t" "text/html" "-selection" "clipboard"))
887 "Program to use to copy HTML to the clipboard.
888Should be a list of strings---the command line.
889Defaults to 'wl-copy' on wayland and 'xclip' on Xorg."
890 :type '(repeat string))
891
892;; Thanks to Oleh Krehel:
893;; https://emacs.stackexchange.com/questions/54292/copy-results-of-org-export-directly-to-clipboard
894;; So. Emacs can't do this itself because it doesn't support sending clipboard
895;; or selection contents as text/html. We have to use xclip instead.
896;; (defun org-to-html-to-clipboard (&rest org-export-args)
897;; "Export current org buffer to HTML, then copy it to the clipboard.
898;; ORG-EXPORT-ARGS are passed to `org-export-to-file'."
899;; (let ((f (make-temp-file "org-html-export")))
900;; (apply #'org-export-to-file 'html f org-export-args)
901;; (start-process "xclip" " *xclip*"
902;; "xclip" "-verbose" "-i" f
903;; "-t" "text/html" "-selection" "clipboard")
904;; (message "HTML pasted to clipboard.")))
905
906(defun org-export-html-copy (&rest org-export-args)
907 "Export current org buffer to HTML and copy to clipboard as rich text.
908ORG-EXPORT-ARGS are passed to `org-export-to-buffer'."
909 (let ((buf (generate-new-buffer "*org-html-clipboard*" t)))
910 (apply #'org-export-to-buffer 'html buf org-export-args)
911 (with-current-buffer buf
912 (apply #'call-process-region
913 (point-min)
914 (point-max)
915 (car clipboard-html-copy-program)
916 nil ; don't delete text
917 nil ; discard the output
918 nil ; don't redisplay
919 (cdr clipboard-html-copy-program))
920 (kill-buffer-and-window))
921 (message "HTML copied to clipboard.")))
922
923;; Wayland version.. TODO: make it work for both
924;; (defun org-to-html-to-clipboard (&rest org-export-args)
925 ;; "Export current org buffer to HTML, then copy it to the clipboard.
926;; ORG-EXPORT-ARGS are passed to `org-export-to-file'."
927 ;; (let ((buf (generate-new-buffer "*org-html-clipboard*" t)))
928 ;; (apply #'org-export-to-buffer 'html buf org-export-args)
929 ;; (with-current-buffer buf
930 ;; (call-process-region (point-min) (point-max)
931 ;; "wl-copy" nil nil nil
932 ;; "-t" "text/html")
933 ;; (kill-buffer-and-window))
934 ;; (message "HTML copied to clipboard.")))
935
936(defun org-subtree-to-html-to-clipboard ()
937 "Export current subtree to HTML."
938 (interactive)
939 (org-export-html-copy nil :subtree))
940
941(undohist-initialize)
942
943(require 'hungry-delete)
944(setopt hungry-delete-chars-to-skip " \t"
945 hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip)
946 hungry-delete-join-reluctantly nil)
947(add-to-list 'hungry-delete-except-modes 'eshell-mode)
948(add-to-list 'hungry-delete-except-modes 'nim-mode)
949(add-to-list 'hungry-delete-except-modes 'python-mode)
950(global-hungry-delete-mode)
951
952(setopt avy-background t
953 avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm"))
954(keymap-global-set "M-j" #'avy-goto-char-timer)
955(keymap-set isearch-mode-map "M-j" #'avy-isearch)
956(keymap-global-set "M-z" #'zzz-to-char)
957
958(marginalia-mode)
959
960(keymap-global-set "C-x b" #'consult-buffer)
961(keymap-global-set "C-x 4 b" #'consult-buffer-other-window)
962(keymap-global-set "C-x 5 b" #'consult-buffer-other-frame)
963(keymap-global-set "C-x r b" #'consult-bookmark)
964(keymap-global-set "M-y" #'consult-yank-pop)
965(keymap-global-set "M-g g" #'consult-goto-line)
966(keymap-global-set "M-g M-g" #'consult-goto-line)
967(keymap-global-set "M-g o" #'consult-outline)
968(keymap-global-set "M-g m" #'consult-mark)
969(keymap-global-set "M-g i" #'consult-imenu)
970(keymap-global-set "M-s d" #'consult-find)
971(keymap-global-set "M-s D" #'consult-locate)
972(keymap-global-set "M-s g" #'consult-grep)
973(keymap-global-set "M-s G" #'consult-git-grep)
974(keymap-global-set "M-s r" #'consult-ripgrep)
975(keymap-global-set "M-s l" #'consult-line)
976(keymap-global-set "M-s k" #'consult-keep-lines)
977(keymap-global-set "M-s u" #'consult-focus-lines)
978
979(keymap-global-set "M-s e" #'consult-isearch-history)
980(keymap-set isearch-mode-map "M-e" #'consult-isearch-history)
981(keymap-set isearch-mode-map "M-s e" #'consult-isearch-history)
982(keymap-set isearch-mode-map "M-s l" #'consult-line)
983
984(keymap-set minibuffer-local-map "M-n" #'consult-history)
985(keymap-set minibuffer-local-map "M-p" #'consult-history)
986
987(setopt completion-in-region-function #'consult-completion-in-region
988 xref-show-xrefs-function #'consult-xref
989 xref-show-definitions-function #'consult-xref)
990
991(setopt initial-scratch-message ";;; Emacs!\n\n")
992
993(keymap-global-set "C-x C-b" #'ibuffer)
994(add-hook 'ibuffer-hook #'hl-line-mode)
995
996(require 'scule)
997(keymap-global-set "M-c" scule-map)
998(autoload 'titlecase-dwim "titlecase" nil t)
999(keymap-set scule-map "M-t" #'titlecase-dwim)
1000
1001;; Use M-u for prefix keys
1002(keymap-global-set "M-u" #'universal-argument)
1003(keymap-set universal-argument-map "M-u" #'universal-argument-more)
1004
1005(autoload 'frowny-mode "frowny" nil t)
1006(add-hook 'jabber-chat-mode-hook #'frowny-mode)
1007(add-hook 'jabber-chat-mode-hook #'electric-pair-local-mode-disable)
1008
1009(autoload 'hippie-completing-read "hippie-completing-read" nil t)
1010(keymap-global-set "M-/" #'hippie-completing-read)
1011
1012(setopt mode-line-bell-flash-time 0.25)
1013(autoload 'mode-line-bell-mode "mode-line-bell" nil t)
1014(mode-line-bell-mode)
1015
1016(keymap-global-set "C-x C-k" #'kill-this-buffer)
1017
1018(require 'anzu)
1019(global-anzu-mode)
1020(setopt search-default-mode t
1021 anzu-mode-lighter ""
1022 anzu-deactivate-region t)
1023
1024(global-set-key [remap query-replace] #'anzu-query-replace-regexp)
1025(global-set-key [remap query-replace-regexp] #'anzu-query-replace)
1026(define-key isearch-mode-map [remap isearch-query-replace]
1027 #'anzu-isearch-query-replace-regexp)
1028(define-key isearch-mode-map [remap isearch-query-replace-regexp]
1029 #'anzu-isearch-query-replace)