Move GNU Emacs configs from ./ into .emacs.d/
[~bandali/configs] / .emacs.d / init.el
1 ;;; init.el --- bandali's emacs configuration -*- lexical-binding: t -*-
2
3 ;; Copyright (C) 2018-2020 Amin Bandali <bandali@gnu.org>
4
5 ;; This program is free software: you can redistribute it and/or modify
6 ;; it under the terms of the GNU General Public License as published by
7 ;; the Free Software Foundation, either version 3 of the License, or
8 ;; (at your option) any later version.
9
10 ;; This program is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ;; GNU General Public License for more details.
14
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18 ;;; Commentary:
19
20 ;; GNU Emacs configuration of Amin Bandali, computer scientist,
21 ;; Free Software activist, and GNU maintainer & webmaster. Packages
22 ;; are installed through using Borg for a fully reproducible setup.
23
24 ;; Over the years, I've taken inspiration from configurations of many
25 ;; great people. Some that I can remember off the top of my head are:
26 ;;
27 ;; - https://github.com/dieggsy/dotfiles
28 ;; - https://github.com/dakra/dmacs
29 ;; - http://pages.sachachua.com/.emacs.d/Sacha.html
30 ;; - https://github.com/dakrone/eos
31 ;; - http://doc.rix.si/cce/cce.html
32 ;; - https://github.com/jwiegley/dot-emacs
33 ;; - https://github.com/wasamasa/dotemacs
34 ;; - https://github.com/hlissner/doom-emacs
35
36 ;;; Code:
37
38 ;;; Emacs initialization
39
40 (defvar b/before-user-init-time (current-time)
41 "Value of `current-time' when Emacs begins loading `user-init-file'.")
42 (defvar b/emacs-initialized nil
43 "Whether Emacs has been initialized.")
44
45 (when (not (bound-and-true-p b/emacs-initialized))
46 (message "Loading Emacs...done (%.3fs)"
47 (float-time (time-subtract b/before-user-init-time
48 before-init-time))))
49
50 ;; temporarily increase `gc-cons-threshhold' and `gc-cons-percentage'
51 ;; during startup to reduce garbage collection frequency. clearing
52 ;; `file-name-handler-alist' seems to help reduce startup time too.
53 (defvar b/gc-cons-threshold gc-cons-threshold)
54 (defvar b/gc-cons-percentage gc-cons-percentage)
55 (defvar b/file-name-handler-alist file-name-handler-alist)
56 (setq gc-cons-threshold (* 30 1024 1024) ; 30 MiB
57 gc-cons-percentage 0.6
58 file-name-handler-alist nil
59 ;; sidesteps a bug when profiling with esup
60 esup-child-profile-require-level 0)
61
62 ;; set them back to their defaults once we're done initializing
63 (defun b/post-init ()
64 "My post-initialize function, run after loading `user-init-file'."
65 (setq b/emacs-initialized t
66 gc-cons-threshold b/gc-cons-threshold
67 gc-cons-percentage b/gc-cons-percentage
68 file-name-handler-alist b/file-name-handler-alist)
69 (when (featurep 'exwm-workspace)
70 (with-eval-after-load 'exwm-workspace
71 (setq-default
72 mode-line-format
73 (append
74 mode-line-format
75 '((:eval
76 (format
77 "[%s]" (number-to-string
78 exwm-workspace-current-index))))))))
79
80 ;; make some mode-line spaces smaller
81 (setq-default
82 mode-line-format
83 (mapcar
84 (lambda (x)
85 (if (and (stringp x) (or (string= x " ") (string= x " ")))
86 " "
87 x))
88 mode-line-format)
89 mode-line-buffer-identification
90 (propertized-buffer-identification "%10b")))
91 (add-hook 'after-init-hook #'b/post-init)
92
93 ;; increase number of lines kept in *Messages* log
94 (setq message-log-max 20000)
95
96 ;; optionally, uncomment to supress some byte-compiler warnings
97 ;; (see C-h v byte-compile-warnings RET for more info)
98 ;; (setq byte-compile-warnings
99 ;; '(not free-vars unresolved noruntime lexical make-local))
100
101 \f
102 ;;; whoami
103
104 (setq user-full-name "Amin Bandali"
105 user-mail-address "bandali@gnu.org")
106
107 \f
108 ;;; csetq (`custom' setq)
109
110 (require 'cl-lib)
111
112 (defmacro csetq (&rest args)
113 "Set the value of user option VAR to VALUE.
114
115 More generally, you can use multiple variables and values, as in
116 (csetq VAR VALUE VAR VALUE...)
117 This sets each user option VAR's value to the corresponding VALUE.
118
119 \(fn [VAR VALUE]...)"
120 (declare (debug setq))
121 `(progn
122 ,@(cl-loop for (var value) on args by 'cddr
123 collect
124 `(funcall (or (get ',var 'custom-set) #'set-default)
125 ',var ,value))))
126
127 \f
128 ;;; Package management
129
130 ;; variables of interest:
131 ;; package-archive-priorities
132 ;; package-load-list
133 ;; package-pinned-packages
134
135 ;; (let* ((b (find-file-noselect "refinery-theme.el"))
136 ;; (d (with-current-buffer b (package-buffer-info))))
137 ;; (package-generate-description-file d "refinery-theme-pkg.el"))
138 (run-with-idle-timer 0.01 nil #'require 'package)
139 (with-eval-after-load 'package
140 (when (= (length package-archives) 1)
141 (csetq
142 package-archives
143 `(,@package-archives
144 ;; ("bndl" . "https://p.bndl.org/elpa/")
145 ("org" . "https://orgmode.org/elpa/"))
146 package-load-list
147 '(;; GNU ELPA
148 (ivy "0.13.1")
149 (counsel "0.13.1")
150 (swiper "0.13.1")
151 (debbugs "0.26")
152 (delight "1.7")
153 (ebdb "0.6.19")
154 (orgalist "1.13")
155 (rt-liberation "1.31")
156 (yasnippet "0.14.0")
157 ;; bndl
158 (refinery-theme "0.1.1")
159 ;; Org ELPA
160 (org-plus-contrib "20201005"))))
161 (package-initialize))
162
163 (csetq package-archive-upload-base "/ssh:caffeine:~/www/p/elpa")
164
165 \f
166 ;;; Initial setup
167
168 ;; keep ~/.emacs.d clean
169 (defvar b/etc-dir
170 (expand-file-name
171 (convert-standard-filename "etc/") user-emacs-directory)
172 "The directory where packages place their configuration files.")
173 (defvar b/var-dir
174 (expand-file-name
175 (convert-standard-filename "var/") user-emacs-directory)
176 "The directory where packages place their persistent data files.")
177 (defvar b/lisp-dir
178 (expand-file-name
179 (convert-standard-filename "lisp/") user-emacs-directory)
180 "The directory where packages place their persistent data files.")
181 (defun b/etc (file)
182 "Expand filename FILE relative to `b/etc-dir'."
183 (expand-file-name (convert-standard-filename file) b/etc-dir))
184 (defun b/var (file)
185 "Expand filename FILE relative to `b/var-dir'."
186 (expand-file-name (convert-standard-filename file) b/var-dir))
187 (defun b/lisp (file)
188 "Expand filename FILE relative to `b/lisp-dir'."
189 (expand-file-name (convert-standard-filename file) b/lisp-dir))
190
191 (csetq
192 auto-save-list-file-prefix (b/var "auto-save/sessions/")
193 nsm-settings-file (b/var "nsm-settings.el"))
194
195 ;; separate custom file (don't want it mixing with init.el)
196 (with-eval-after-load 'custom
197 (setq custom-file (b/etc "custom.el"))
198 (when (file-exists-p custom-file)
199 (load custom-file))
200 ;; while at it, treat themes as safe
201 ;; (setf custom-safe-themes t)
202 ;; only one custom theme at a time
203 ;; (defadvice load-theme (before clear-previous-themes activate)
204 ;; "Clear existing theme settings instead of layering them"
205 ;; (mapc #'disable-theme custom-enabled-themes))
206 )
207
208 ;; load the secrets file if it exists, otherwise show a warning
209 ;; (with-demoted-errors
210 ;; (load (b/etc "secrets")))
211
212 ;; start up emacs server. see
213 ;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html#Emacs-Server
214 (run-with-idle-timer 0.5 nil #'require 'server)
215 (with-eval-after-load 'server
216 (declare-function server-edit "server")
217 (global-set-key (kbd "C-c F D") #'server-edit)
218 (declare-function server-running-p "server")
219 (or (server-running-p) (server-mode)))
220
221 \f
222 ;;; Defaults
223
224 ;;;; C-level customizations
225
226 (csetq
227 ;; minibuffer
228 enable-recursive-minibuffers t
229 resize-mini-windows t
230 ;; more useful frame titles
231 ;; frame-title-format '("" invocation-name " - "
232 ;; (:eval
233 ;; (if (buffer-file-name)
234 ;; (abbreviate-file-name (buffer-file-name))
235 ;; "%b")))
236 ;; i don't feel like jumping out of my chair every now and again; so
237 ;; don't BEEP! at me, emacs
238 ring-bell-function 'ignore
239 ;; better scrolling
240 ;; scroll-margin 1
241 ;; scroll-conservatively 10000
242 scroll-step 1
243 scroll-conservatively 101
244 scroll-preserve-screen-position 1
245 ;; focus follows mouse
246 mouse-autoselect-window t)
247
248 (setq-default
249 ;; always use space for indentation
250 indent-tabs-mode nil
251 tab-width 4
252 ;; case-sensitive search (and `dabbrev-expand')
253 ;; case-fold-search nil
254 ;; cursor shape
255 cursor-type t)
256
257 (set-fontset-font t 'arabic "Vazir")
258
259 ;; unicode support
260 ;; (dolist (ft (fontset-list))
261 ;; (set-fontset-font
262 ;; ft
263 ;; 'unicode
264 ;; (font-spec :name "Source Code Pro" :size 14))
265 ;; (set-fontset-font
266 ;; ft
267 ;; 'unicode
268 ;; (font-spec :name "DejaVu Sans Mono")
269 ;; nil
270 ;; 'append)
271 ;; ;; (set-fontset-font
272 ;; ;; ft
273 ;; ;; 'unicode
274 ;; ;; (font-spec
275 ;; ;; :name "Symbola monospacified for DejaVu Sans Mono")
276 ;; ;; nil
277 ;; ;; 'append)
278 ;; ;; (set-fontset-font
279 ;; ;; ft
280 ;; ;; #x2115 ; ℕ
281 ;; ;; (font-spec :name "DejaVu Sans Mono")
282 ;; ;; nil
283 ;; ;; 'append)
284 ;; (set-fontset-font
285 ;; ft
286 ;; (cons ?Α ?ω)
287 ;; (font-spec :name "DejaVu Sans Mono" :size 14)
288 ;; nil
289 ;; 'prepend))
290
291 ;;;; Elisp-level customizations
292
293 ;; startup
294 ;; don't need to see the startup echo area message
295 (advice-add #'display-startup-echo-area-message :override #'ignore)
296 (csetq
297 ;; i want *scratch* as my startup buffer
298 initial-buffer-choice t
299 ;; i don't need the default hint
300 initial-scratch-message nil
301 ;; use customizable text-mode as major mode for *scratch*
302 ;; (initial-major-mode 'text-mode)
303 ;; inhibit buffer list when more than 2 files are loaded
304 inhibit-startup-buffer-menu t
305 ;; don't need to see the startup screen or echo area message
306 inhibit-startup-screen t
307 inhibit-startup-echo-area-message user-login-name)
308
309 ;; files
310 (csetq
311 ;; backups (C-h v make-backup-files RET)
312 backup-by-copying t
313 backup-directory-alist (list (cons "." (b/var "backup/")))
314 version-control t
315 delete-old-versions t
316 ;; auto-save
317 auto-save-file-name-transforms `((".*" ,(b/var "auto-save/") t))
318 ;; insert newline at the end of files
319 require-final-newline t
320 ;; open read-only file buffers in view-mode
321 ;; (enables niceties like `q' for quit)
322 view-read-only t)
323
324 ;; novice
325 ;; disable disabled commands
326 (csetq disabled-command-function nil)
327
328 ;; lazy-person-friendly yes/no prompts
329 (defalias 'yes-or-no-p #'y-or-n-p)
330
331 ;; autorevert: enable automatic reloading of changed buffers and files
332 (csetq auto-revert-verbose nil
333 global-auto-revert-non-file-buffers nil)
334 (require 'autorevert)
335 (global-auto-revert-mode 1)
336
337 ;; time and battery in mode-line
338 (csetq
339 display-time-default-load-average nil
340 display-time-format " %a %b %-e %-l:%M%P"
341 display-time-mail-icon '(image :type xpm
342 :file "gnus/gnus-pointer.xpm"
343 :ascent center)
344 display-time-use-mail-icon t)
345 (require 'time)
346 (display-time-mode)
347
348 (csetq battery-mode-line-format " %p%% %t")
349 (require 'battery)
350 (display-battery-mode)
351
352 (require 'fringe)
353 ;; smaller fringe
354 ;; (fringe-mode '(3 . 1))
355 (fringe-mode nil)
356
357 (require 'winner)
358 ;; enable winner-mode (C-h f winner-mode RET)
359 (winner-mode 1)
360
361 (with-eval-after-load 'compile
362 ;; don't display *compilation* buffer on success. based on
363 ;; https://stackoverflow.com/a/17788551, with changes to use `cl-letf'
364 ;; instead of the now obsolete `flet'.
365 (defun b/compilation-finish-function (buffer outstr)
366 (unless (string-match "finished" outstr)
367 (switch-to-buffer-other-window buffer))
368 t)
369
370 (setq compilation-finish-functions #'b/compilation-finish-function)
371
372 (require 'cl-macs)
373
374 (defadvice compilation-start
375 (around inhibit-display
376 (command &optional mode name-function highlight-regexp))
377 (if (not (string-match "^\\(find\\|grep\\)" command))
378 (cl-letf (((symbol-function 'display-buffer) #'ignore))
379 (save-window-excursion ad-do-it))
380 ad-do-it))
381 (ad-activate 'compilation-start))
382
383 ;; isearch
384 (csetq
385 ;; allow scrolling in Isearch
386 isearch-allow-scroll t
387 ;; search for non-ASCII characters: i’d like non-ASCII characters such
388 ;; as ‘’“”«»‹›áⓐ𝒶 to be selected when i search for their ASCII
389 ;; counterpart. shoutout to
390 ;; http://endlessparentheses.com/new-in-emacs-25-1-easily-search-non-ascii-characters.html
391 search-default-mode #'char-fold-to-regexp)
392
393 ;; replace
394 ;; uncomment to extend the above behaviour to query-replace
395 ;; (csetq replace-char-fold t)
396
397 ;; vc
398 (global-set-key (kbd "C-x v C-=") #'vc-ediff)
399
400 (with-eval-after-load 'vc-git
401 (csetq vc-git-print-log-follow t
402 vc-git-show-stash 0))
403
404 (csetq ediff-window-setup-function 'ediff-setup-windows-plain
405 ediff-split-window-function 'split-window-horizontally)
406 (with-eval-after-load 'ediff
407 (add-hook 'ediff-after-quit-hook-internal #'winner-undo))
408
409 ;; face-remap
410 (csetq
411 ;; gentler font resizing
412 text-scale-mode-step 1.05)
413
414 (run-with-idle-timer 0.4 nil #'require 'mwheel)
415 (csetq mouse-wheel-scroll-amount '(1 ((shift) . 1)) ; one line at a time
416 mouse-wheel-progressive-speed nil ; don't accelerate scrolling
417 mouse-wheel-follow-mouse t) ; scroll window under mouse
418
419 (run-with-idle-timer 0.4 nil #'require 'pixel-scroll)
420 (with-eval-after-load 'pixel-scroll
421 (pixel-scroll-mode 1))
422
423 ;; epg-config
424 (csetq
425 epg-gpg-program (executable-find "gpg")
426 ;; ask for GPG passphrase in minibuffer
427 ;; this will fail if gpg>=2.1 is not available
428 epg-pinentry-mode 'loopback)
429
430 ;; (require 'pinentry)
431 ;; workaround for systemd-based distros:
432 ;; (setq pinentry--socket-dir server-socket-dir)
433 ;; (pinentry-start)
434
435 ;; auth-source
436 (csetq
437 auth-sources '("~/.authinfo.gpg")
438 authinfo-hidden (regexp-opt '("password" "client-secret" "token")))
439
440 \f
441 ;;; Useful utilities
442
443 (defun b/add-elisp-section ()
444 (interactive)
445 (insert "\n")
446 (forward-line -1)
447 (insert "\n\f\n;;; "))
448
449 (defun b/insert-asterism ()
450 "Insert a centred asterism."
451 (interactive)
452 (let ((asterism "* * *"))
453 (insert
454 (concat
455 "\n"
456 (make-string
457 (floor (/ (- fill-column (length asterism)) 2))
458 ?\s)
459 asterism
460 "\n"))))
461
462 (defun b/start-process (program &rest args)
463 "Same as `start-process', but doesn't bother about name and buffer."
464 (let ((process-name (concat program "_process"))
465 (buffer-name (generate-new-buffer-name
466 (concat program "_output"))))
467 (apply #'start-process
468 process-name buffer-name program args)))
469
470 (defun b/no-mouse-autoselect-window ()
471 "Conveniently disable `focus-follows-mouse'.
472 For disabling the behaviour for certain buffers and/or modes."
473 (make-local-variable 'mouse-autoselect-window)
474 (setq mouse-autoselect-window nil))
475
476 (defun b/kill-current-buffer ()
477 "Kill the current buffer."
478 ;; also see https://redd.it/64xb3q
479 (interactive)
480 (kill-buffer (current-buffer)))
481
482 (defun b/move-indentation-or-beginning-of-line (arg)
483 "Move to the indentation or to the beginning of line."
484 (interactive "^p")
485 ;; (if (bolp)
486 ;; (back-to-indentation)
487 ;; (move-beginning-of-line arg))
488 (if (= (point)
489 (progn (back-to-indentation)
490 (point)))
491 (move-beginning-of-line arg)))
492
493 (defun b/join-line-top ()
494 "Like `join-line', but join next line to the current line."
495 (interactive)
496 (join-line 1))
497
498 (defun b/duplicate-line-or-region (&optional n)
499 "Duplicate the current line, or region (if active).
500 Make N (default: 1) copies of the current line or region."
501 (interactive "*p")
502 (let ((u-r-p (use-region-p)) ; if region is active
503 (n1 (or n 1)))
504 (save-excursion
505 (let ((text
506 (if u-r-p
507 (buffer-substring (region-beginning) (region-end))
508 (prog1 (thing-at-point 'line)
509 (end-of-line)
510 (if (eobp)
511 (newline)
512 (forward-line 1))))))
513 (dotimes (_ (abs n1))
514 (insert text))))))
515
516 \f
517 ;;; General key bindings
518
519 (global-set-key (kbd "C-a") #'b/move-indentation-or-beginning-of-line)
520 (global-set-key (kbd "C-c a i") #'ielm)
521 (global-set-key (kbd "C-c d") #'b/duplicate-line-or-region)
522 (global-set-key (kbd "C-S-j") #'b/join-line-top)
523 (global-set-key (kbd "C-c x") #'execute-extended-command)
524
525 ;; evaling and macro-expanding
526 (global-set-key (kbd "C-c e b") #'eval-buffer)
527 (global-set-key (kbd "C-c e e") #'eval-last-sexp)
528 (global-set-key (kbd "C-c e p") #'pp-macroexpand-last-sexp)
529 (global-set-key (kbd "C-c e r") #'eval-region)
530
531 ;; emacs things
532 (global-set-key (kbd "C-c e i") #'emacs-init-time)
533 (global-set-key (kbd "C-c e u") #'emacs-uptime)
534 (global-set-key (kbd "C-c e v") #'emacs-version)
535
536 ;; finding
537 (global-set-key (kbd "C-c f .") #'find-file)
538 (global-set-key (kbd "C-c f d") #'find-name-dired)
539 (global-set-key (kbd "C-c f l") #'find-library)
540
541 ;; frames
542 (global-set-key (kbd "C-c F m") #'make-frame-command)
543 (global-set-key (kbd "C-c F d") #'delete-frame)
544
545 ;; help/describe
546 (global-set-key (kbd "C-S-h C") #'describe-char)
547 (global-set-key (kbd "C-S-h F") #'describe-face)
548
549 ;; (global-set-key (kbd "C-x k") #'b/kill-current-buffer)
550 ;; (global-set-key (kbd "C-x K") #'kill-buffer)
551 ;; (global-set-key (kbd "C-x s") #'save-buffer)
552 ;; (global-set-key (kbd "C-x S") #'save-some-buffers)
553
554 (define-key emacs-lisp-mode-map (kbd "<C-return>") #'b/add-elisp-section)
555
556 (when (display-graphic-p)
557 (global-unset-key (kbd "C-z")))
558
559 \f
560 ;;; Essential packages
561
562 (add-to-list
563 'load-path
564 (expand-file-name
565 (convert-standard-filename "lisp") user-emacs-directory))
566
567 ;; (require 'bandali-exwm)
568
569 (require 'bandali-org)
570
571 (require 'bandali-theme)
572
573 ;; (require 'bandali-magit)
574
575 ;; recently opened files
576 (csetq recentf-max-saved-items 2000
577 recentf-save-file (b/var "recentf-save.el"))
578 (run-with-idle-timer 0.2 nil #'require 'recentf)
579 (with-eval-after-load 'recentf
580 ;; (add-to-list 'recentf-keep #'file-remote-p)
581 (recentf-mode))
582
583 ;; needed for history for counsel
584 (csetq amx-save-file (b/var "amx-save.el"))
585 (add-to-list 'load-path (b/lisp "s"))
586 (add-to-list 'load-path (b/lisp "amx"))
587 (run-with-idle-timer 0.3 nil #'require 'amx)
588 (with-eval-after-load 'amx
589 (amx-mode))
590
591 (require 'bandali-ivy)
592
593 (require 'bandali-eshell)
594
595 (require 'bandali-ibuffer)
596
597 ;; outline
598 ;; (with-eval-after-load 'outline
599 ;; (when (featurep 'which-key)
600 ;; (which-key-add-key-based-replacements
601 ;; "C-c @" "outline"
602 ;; "s-O" "outline"))
603 ;; (define-key outline-minor-mode-map (kbd "<s-tab>")
604 ;; #'outline-toggle-children)
605 ;; (define-key outline-minor-mode-map (kbd "M-p")
606 ;; #'outline-previous-visible-heading)
607 ;; (define-key outline-minor-mode-map (kbd "M-n")
608 ;; #'outline-next-visible-heading)
609 ;; (defvar b/outline-prefix-map)
610 ;; (define-prefix-command 'b/outline-prefix-map)
611 ;; (define-key outline-minor-mode-map (kbd "s-O")
612 ;; 'b/outline-prefix-map)
613 ;; (define-key b/outline-prefix-map (kbd "TAB")
614 ;; #'outline-toggle-children)
615 ;; (define-key b/outline-prefix-map (kbd "a")
616 ;; #'outline-hide-body)
617 ;; (define-key b/outline-prefix-map (kbd "H")
618 ;; #'outline-hide-body)
619 ;; (define-key b/outline-prefix-map (kbd "S")
620 ;; #'outline-show-all)
621 ;; (define-key b/outline-prefix-map (kbd "h")
622 ;; #'outline-hide-subtree)
623 ;; (define-key b/outline-prefix-map (kbd "s")
624 ;; #'outline-show-subtree))
625 ;; (add-hook 'prog-mode-hook #'outline-minor-mode)
626
627 (require 'bandali-dired)
628
629 (with-eval-after-load 'help
630 (temp-buffer-resize-mode)
631 (csetq help-window-select t))
632
633 (with-eval-after-load 'help-mode
634 ;; local key bindings
635 (define-key help-mode-map (kbd "p") #'backward-button)
636 (define-key help-mode-map (kbd "n") #'forward-button))
637
638 (with-eval-after-load 'tramp
639 (csetq tramp-auto-save-directory (b/var "tramp/auto-save/")
640 tramp-persistency-file-name (b/var "tramp/persistency.el"))
641 (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:"))
642 (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
643 (add-to-list 'tramp-default-proxies-alist
644 (list (regexp-quote (system-name)) nil nil)))
645
646 (with-eval-after-load 'doc-view
647 (define-key doc-view-mode-map (kbd "M-RET") #'image-previous-line))
648
649 (csetq shr-max-width 80)
650
651 ;; Email (with Gnus, message, and EBDB)
652 (require 'bandali-gnus)
653 (with-eval-after-load 'sendmail
654 (csetq sendmail-program (executable-find "msmtp")
655 ;; message-sendmail-extra-arguments '("-v" "-d")
656 mail-specify-envelope-from t
657 mail-envelope-from 'header))
658 (require 'bandali-message)
659 (require 'bandali-ebdb)
660
661 ;; IRC (with ERC)
662 (require 'bandali-erc)
663
664 ;; 'paste' service (aka scp + web server)
665 (add-to-list 'load-path (b/lisp "scpaste"))
666 (with-eval-after-load 'scpaste
667 (csetq scpaste-http-destination "https://p.bndl.org"
668 scpaste-scp-destination "p:~"))
669 (autoload 'scpaste "scpaste" nil t)
670 (autoload 'scpaste-region "scpaste" nil t)
671 (global-set-key (kbd "C-c a p p") #'scpaste)
672 (global-set-key (kbd "C-c a p r") #'scpaste-region)
673
674 \f
675 ;;; Editing
676
677 ;; display Lisp objects at point in the echo area
678 (when (version< "25" emacs-version)
679 (with-eval-after-load 'eldoc
680 (csetq eldoc-minor-mode-string " eldoc")
681 (global-eldoc-mode)))
682
683 ;; highlight matching parens
684 (require 'paren)
685 (show-paren-mode)
686
687 ;; (require 'elec-pair)
688 ;; (electric-pair-mode)
689
690 (csetq
691 ;; Save what I copy into clipboard from other applications into Emacs'
692 ;; kill-ring, which would allow me to still be able to easily access
693 ;; it in case I kill (cut or copy) something else inside Emacs before
694 ;; yanking (pasting) what I'd originally intended to.
695 save-interprogram-paste-before-kill t)
696 (with-eval-after-load 'simple
697 (column-number-mode 1))
698
699 ;; save minibuffer history
700 (require 'savehist)
701 (csetq savehist-file (b/var "savehist.el"))
702 (savehist-mode)
703 (add-to-list 'savehist-additional-variables 'kill-ring)
704
705 ;; automatically save place in files
706 (when (version< "25" emacs-version)
707 (csetq save-place-file (b/var "save-place.el"))
708 (save-place-mode))
709
710 (defun indicate-buffer-boundaries-left ()
711 (csetq indicate-buffer-boundaries 'left))
712 (with-eval-after-load 'prog-mode
713 (global-prettify-symbols-mode))
714 (add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left)
715
716 (define-key text-mode-map (kbd "C-<return>") #'b/insert-asterism)
717 (add-hook 'text-mode-hook #'indicate-buffer-boundaries-left)
718 (add-hook 'text-mode-hook #'flyspell-mode)
719
720 (add-to-list 'auto-mode-alist '("\\.*rc$" . conf-mode))
721
722 (add-to-list 'auto-mode-alist '("\\.bashrc$" . sh-mode))
723
724 (with-eval-after-load 'flyspell
725 (csetq flyspell-mode-line-string " fly"))
726
727 ;; flycheck
728 ;; (run-with-idle-timer 0.6 nil #'require 'flycheck)
729 ;; (with-eval-after-load 'flycheck
730 ;; (csetq
731 ;; ;; Use the load-path from running Emacs when checking elisp files
732 ;; flycheck-emacs-lisp-load-path 'inherit
733 ;; ;; Only flycheck when I actually save the buffer
734 ;; flycheck-check-syntax-automatically '(mode-enabled save)
735 ;; flycheck-mode-line-prefix "flyc"))
736 ;; (define-key flycheck-mode-map (kbd "M-P") #'flycheck-previous-error)
737 ;; (define-key flycheck-mode-map (kbd "M-N") #'flycheck-next-error)
738 ;; (add-hook 'prog-mode-hook #'flycheck-mode)
739
740 ;; ispell
741 ;; http://endlessparentheses.com/ispell-and-apostrophes.html
742 ;; (run-with-idle-timer 0.6 nil #'require 'ispell)
743 ;; (with-eval-after-load 'ispell
744 ;; ;; ’ can be part of a word
745 ;; (csetq ispell-local-dictionary-alist
746 ;; `((nil "[[:alpha:]]" "[^[:alpha:]]"
747 ;; "['\x2019]" nil ("-B") nil utf-8))
748 ;; ispell-program-name (executable-find "hunspell"))
749 ;; ;; don't send ’ to the subprocess
750 ;; (defun endless/replace-apostrophe (args)
751 ;; (cons (replace-regexp-in-string
752 ;; "’" "'" (car args))
753 ;; (cdr args)))
754 ;; (advice-add #'ispell-send-string :filter-args
755 ;; #'endless/replace-apostrophe)
756 ;; ;; convert ' back to ’ from the subprocess
757 ;; (defun endless/replace-quote (args)
758 ;; (if (not (derived-mode-p 'org-mode))
759 ;; args
760 ;; (cons (replace-regexp-in-string
761 ;; "'" "’" (car args))
762 ;; (cdr args))))
763 ;; (advice-add #'ispell-parse-output :filter-args
764 ;; #'endless/replace-quote))
765
766 ;; abbrev
767 (csetq abbrev-file-name (b/etc "abbrev.el"))
768 (add-hook 'text-mode-hook #'abbrev-mode)
769
770 \f
771 ;;; Programming modes
772
773 (with-eval-after-load 'lisp-mode
774 (defun indent-spaces-mode ()
775 (setq indent-tabs-mode nil))
776 (add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode))
777
778 ;; alloy
779 (add-to-list 'load-path (b/lisp "alloy-mode"))
780 (autoload 'alloy-mode "alloy-mode" nil t)
781 (with-eval-after-load 'alloy-mode
782 (csetq alloy-basic-offset 2)
783 ;; (defun b/alloy-simple-indent (start end)
784 ;; (interactive "r")
785 ;; ;; (if (region-active-p)
786 ;; ;; (indent-rigidly start end alloy-basic-offset)
787 ;; ;; (if (bolp)
788 ;; ;; (indent-rigidly (line-beginning-position)
789 ;; ;; (line-end-position)
790 ;; ;; alloy-basic-offset)))
791 ;; (indent-to (+ (current-column) alloy-basic-offset)))
792 ;; local key bindings
793 (define-key alloy-mode-map (kbd "RET") #'electric-newline-and-maybe-indent)
794 ;; (define-key alloy-mode-map (kbd "TAB") #'b/alloy-simple-indent)
795 (define-key alloy-mode-map (kbd "TAB") #'indent-for-tab-command))
796 (add-to-list 'auto-mode-alist '("\\.\\(als\\|dsh\\)\\'" . alloy-mode))
797 (add-hook 'alloy-mode-hook (lambda nil (setq-local indent-tabs-mode nil)))
798
799 ;; lean
800 ;; (eval-when-compile (defvar lean-mode-map))
801 ;; (run-with-idle-timer 0.4 nil #'require 'lean-mode)
802 ;; (with-eval-after-load 'lean-mode
803 ;; (require 'lean-input)
804 ;; (csetq default-input-method "Lean"
805 ;; lean-input-tweak-all '(lean-input-compose
806 ;; (lean-input-prepend "/")
807 ;; (lean-input-nonempty))
808 ;; lean-input-user-translations '(("/" "/")))
809 ;; (lean-input-setup)
810 ;; ;; local key bindings
811 ;; (define-key lean-mode-map (kbd "S-SPC") #'company-complete))
812
813 (with-eval-after-load 'sgml-mode
814 (csetq sgml-basic-offset 0))
815
816 (with-eval-after-load 'css-mode
817 (csetq css-indent-offset 2))
818
819 ;; po-mode
820 ;; (add-hook 'po-mode-hook (lambda nil (run-with-timer 0.1 nil 'View-exit)))
821
822 ;; auctex
823 ;; (csetq font-latex-fontify-sectioning 'color)
824
825 (with-eval-after-load 'tex-mode
826 (cl-delete-if
827 (lambda (p) (string-match "^---?" (car p)))
828 tex--prettify-symbols-alist))
829 (add-hook 'tex-mode-hook #'auto-fill-mode)
830 (add-hook 'tex-mode-hook #'flyspell-mode)
831
832 \f
833 ;;; Emacs enhancements & auxiliary packages
834
835 (with-eval-after-load 'man
836 (csetq Man-width 80))
837
838 (defun b/*scratch* ()
839 "Switch to `*scratch*' buffer, creating it if it does not exist."
840 (interactive)
841 (switch-to-buffer
842 (or (get-buffer "*scratch*")
843 (with-current-buffer (get-buffer-create "*scratch*")
844 (set-buffer-major-mode (current-buffer))
845 (current-buffer)))))
846 (global-set-key (kbd "C-c s") #'b/*scratch*)
847
848 ;; ,----
849 ;; | make pretty boxed quotes like this
850 ;; `----
851 (add-to-list 'load-path (b/lisp "boxquote"))
852 (run-with-idle-timer 0.6 nil #'require 'boxquote)
853 (with-eval-after-load 'boxquote
854 (defvar b/boxquote-prefix-map)
855 (define-prefix-command 'b/boxquote-prefix-map)
856 (global-set-key (kbd "C-c q") 'b/boxquote-prefix-map)
857 (define-key b/boxquote-prefix-map (kbd "b") #'boxquote-buffer)
858 (define-key b/boxquote-prefix-map (kbd "B") #'boxquote-insert-buffer)
859 (define-key b/boxquote-prefix-map (kbd "d") #'boxquote-defun)
860 (define-key b/boxquote-prefix-map (kbd "F") #'boxquote-insert-file)
861 (define-key b/boxquote-prefix-map (kbd "hf") #'boxquote-describe-function)
862 (define-key b/boxquote-prefix-map (kbd "hk") #'boxquote-describe-key)
863 (define-key b/boxquote-prefix-map (kbd "hv") #'boxquote-describe-variable)
864 (define-key b/boxquote-prefix-map (kbd "hw") #'boxquote-where-is)
865 (define-key b/boxquote-prefix-map (kbd "k") #'boxquote-kill)
866 (define-key b/boxquote-prefix-map (kbd "p") #'boxquote-paragraph)
867 (define-key b/boxquote-prefix-map (kbd "q") #'boxquote-boxquote)
868 (define-key b/boxquote-prefix-map (kbd "r") #'boxquote-region)
869 (define-key b/boxquote-prefix-map (kbd "s") #'boxquote-shell-command)
870 (define-key b/boxquote-prefix-map (kbd "t") #'boxquote-text)
871 (define-key b/boxquote-prefix-map (kbd "T") #'boxquote-title)
872 (define-key b/boxquote-prefix-map (kbd "u") #'boxquote-unbox)
873 (define-key b/boxquote-prefix-map (kbd "U") #'boxquote-unbox-region)
874 (define-key b/boxquote-prefix-map (kbd "y") #'boxquote-yank)
875 (define-key b/boxquote-prefix-map (kbd "M-q") #'boxquote-fill-paragraph)
876 (define-key b/boxquote-prefix-map (kbd "M-w") #'boxquote-kill-ring-save))
877
878 (add-to-list 'load-path (b/lisp "hl-todo"))
879 (run-with-idle-timer 0.5 nil #'require 'hl-todo)
880 (with-eval-after-load 'hl-todo
881 ;; highlight TODOs in buffers
882 (global-hl-todo-mode))
883
884 (add-to-list 'load-path (b/lisp "page-break-lines"))
885 (run-with-idle-timer 0.5 nil #'require 'page-break-lines)
886 (with-eval-after-load 'page-break-lines
887 (csetq page-break-lines-max-width fill-column)
888 (global-page-break-lines-mode))
889
890 ;; expand-region
891 (global-set-key (kbd "C-=") #'er/expand-region)
892
893 (run-with-idle-timer 0.6 nil #'require 'yasnippet)
894 (with-eval-after-load 'yasnippet
895 (declare-function yas-reload-all
896 "yasnippet" (&optional no-jit interactive))
897 (declare-function yas-maybe-expand-abbrev-key-filter
898 "yasnippet" (cmd))
899
900 (defconst yas-verbosity-cur yas-verbosity)
901 (setq yas-verbosity 2)
902 (csetq yas-snippet-dirs `(,(b/etc "yasnippet/snippets")))
903 (add-to-list 'yas-snippet-dirs "~/src/git/guix/etc/snippets" t)
904 (yas-reload-all)
905 (setq yas-verbosity yas-verbosity-cur)
906
907 (defun b/yas-maybe-expand-abbrev-key-filter (cmd)
908 (when (and (yas-maybe-expand-abbrev-key-filter cmd)
909 (not (bound-and-true-p git-commit-mode)))
910 cmd))
911 (defconst b/yas-maybe-expand
912 '(menu-item "" yas-expand
913 :filter b/yas-maybe-expand-abbrev-key-filter))
914 (define-key yas-minor-mode-map (kbd "SPC") b/yas-maybe-expand)
915
916 (yas-global-mode))
917
918 ;; debbugs
919 (global-set-key (kbd "C-c D d") #'debbugs-gnu)
920 (global-set-key (kbd "C-c D b") #'debbugs-gnu-bugs)
921 (global-set-key (kbd "C-c D e") ; bug-gnu-emacs
922 (lambda ()
923 (interactive)
924 (setq debbugs-gnu-current-suppress t)
925 (debbugs-gnu debbugs-gnu-default-severities
926 '("emacs"))))
927 (global-set-key (kbd "C-c D g") ; bug-gnuzilla
928 (lambda ()
929 (interactive)
930 (setq debbugs-gnu-current-suppress t)
931 (debbugs-gnu debbugs-gnu-default-severities
932 '("gnuzilla"))))
933 (global-set-key (kbd "C-c D G b") ; bug-guix
934 (lambda ()
935 (interactive)
936 (setq debbugs-gnu-current-suppress t)
937 (debbugs-gnu debbugs-gnu-default-severities
938 '("guix"))))
939 (global-set-key (kbd "C-c D G p") ; guix-patches
940 (lambda ()
941 (interactive)
942 (setq debbugs-gnu-current-suppress t)
943 (debbugs-gnu debbugs-gnu-default-severities
944 '("guix-patches"))))
945
946 ;; url and url-cache
947 (csetq
948 url-configuration-directory (b/var "url/configuration/")
949 url-cache-directory (b/var "url/cache/"))
950
951 ;; eww
952 (csetq eww-download-directory (file-name-as-directory
953 (getenv "XDG_DOWNLOAD_DIR")))
954 (global-set-key (kbd "C-c a e w") #'eww)
955
956 ;; ;; org-ref
957 ;; (csetq
958 ;; reftex-default-bibliography '("~/usr/org/references.bib")
959 ;; org-ref-default-bibliography '("~/usr/org/references.bib")
960 ;; org-ref-bibliography-notes "~/usr/org/notes.org"
961 ;; org-ref-pdf-directory "~/usr/org/bibtex-pdfs/")
962
963 ;; fill-column-indicator ?
964
965 ;; window
966 (csetq split-width-threshold 150)
967 (global-set-key (kbd "C-c w s l")
968 (lambda ()
969 (interactive)
970 (split-window-right)
971 (other-window 1)))
972 (global-set-key (kbd "C-c w s j")
973 (lambda ()
974 (interactive)
975 (split-window-below)
976 (other-window 1)))
977 (global-set-key (kbd "C-c w q") #'quit-window)
978
979 (run-with-idle-timer 0.6 nil #'require 'windmove)
980 (global-set-key (kbd "C-c w h") #'windmove-left)
981 (global-set-key (kbd "C-c w j") #'windmove-down)
982 (global-set-key (kbd "C-c w k") #'windmove-up)
983 (global-set-key (kbd "C-c w l") #'windmove-right)
984 (global-set-key (kbd "C-c w H") #'windmove-swap-states-left)
985 (global-set-key (kbd "C-c w J") #'windmove-swap-states-down)
986 (global-set-key (kbd "C-c w K") #'windmove-swap-states-up)
987 (global-set-key (kbd "C-c w L") #'windmove-swap-states-right)
988
989 ;; pass
990 ;; (global-set-key (kbd "C-c a p") #'pass)
991 ;; (add-hook 'pass-mode-hook #'View-exit)
992
993 ;; reftex
994 ;; uncomment to disable reftex-cite's default choice of previous word
995 ;; (with-eval-after-load 'reftex
996 ;; (require 'reftex-cite)
997 ;; (defun reftex-get-bibkey-default ()
998 ;; "If the cursor is in a citation macro, return the word before the macro."
999 ;; (let* ((macro (reftex-what-macro 1)))
1000 ;; (save-excursion
1001 ;; (when (and macro (string-match "cite" (car macro)))
1002 ;; (goto-char (cdr macro)))
1003 ;; (reftex-this-word)))))
1004 (add-hook 'latex-mode-hook #'reftex-mode)
1005
1006 ;; dmenu
1007 ;; (csetq
1008 ;; dmenu-prompt-string "run: "
1009 ;; dmenu-save-file (b/var "dmenu-items"))
1010
1011 ;; eosd ?
1012
1013 ;; delight
1014 (run-with-idle-timer 0.5 nil #'require 'delight)
1015 (with-eval-after-load 'delight
1016 (delight 'auto-fill-function " f" "simple")
1017 (delight 'abbrev-mode "" "abbrev")
1018 (delight 'page-break-lines-mode "" "page-break-lines")
1019 (delight 'ivy-mode "" "ivy")
1020 (delight 'counsel-mode "" "counsel")
1021 (delight 'mml-mode " mml" "mml")
1022 (delight 'yas-minor-mode "" "yasnippet"))
1023
1024 \f
1025 ;;; Post initialization
1026
1027 (message "Loading %s...done (%.3fs)" user-init-file
1028 (float-time (time-subtract (current-time)
1029 b/before-user-init-time)))
1030
1031 ;;; init.el ends here