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