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