997e6cf418d6a0a140d63a7109b37a361392b114
[~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 ;; recently opened files
572 (csetq recentf-max-saved-items 2000
573 recentf-save-file (b/var "recentf-save.el"))
574 (run-with-idle-timer 0.2 nil #'require 'recentf)
575 (with-eval-after-load 'recentf
576 ;; (add-to-list 'recentf-keep #'file-remote-p)
577 (recentf-mode))
578
579 (fido-mode 1)
580
581 (require 'bandali-eshell)
582
583 (require 'bandali-ibuffer)
584
585 ;; outline
586 ;; (with-eval-after-load 'outline
587 ;; (when (featurep 'which-key)
588 ;; (which-key-add-key-based-replacements
589 ;; "C-c @" "outline"
590 ;; "s-O" "outline"))
591 ;; (define-key outline-minor-mode-map (kbd "<s-tab>")
592 ;; #'outline-toggle-children)
593 ;; (define-key outline-minor-mode-map (kbd "M-p")
594 ;; #'outline-previous-visible-heading)
595 ;; (define-key outline-minor-mode-map (kbd "M-n")
596 ;; #'outline-next-visible-heading)
597 ;; (defvar b/outline-prefix-map)
598 ;; (define-prefix-command 'b/outline-prefix-map)
599 ;; (define-key outline-minor-mode-map (kbd "s-O")
600 ;; 'b/outline-prefix-map)
601 ;; (define-key b/outline-prefix-map (kbd "TAB")
602 ;; #'outline-toggle-children)
603 ;; (define-key b/outline-prefix-map (kbd "a")
604 ;; #'outline-hide-body)
605 ;; (define-key b/outline-prefix-map (kbd "H")
606 ;; #'outline-hide-body)
607 ;; (define-key b/outline-prefix-map (kbd "S")
608 ;; #'outline-show-all)
609 ;; (define-key b/outline-prefix-map (kbd "h")
610 ;; #'outline-hide-subtree)
611 ;; (define-key b/outline-prefix-map (kbd "s")
612 ;; #'outline-show-subtree))
613 ;; (add-hook 'prog-mode-hook #'outline-minor-mode)
614
615 (require 'bandali-dired)
616
617 (with-eval-after-load 'help
618 (temp-buffer-resize-mode)
619 (csetq help-window-select t))
620
621 (with-eval-after-load 'help-mode
622 ;; local key bindings
623 (define-key help-mode-map (kbd "p") #'backward-button)
624 (define-key help-mode-map (kbd "n") #'forward-button))
625
626 (with-eval-after-load 'tramp
627 (csetq tramp-auto-save-directory (b/var "tramp/auto-save/")
628 tramp-persistency-file-name (b/var "tramp/persistency.el"))
629 (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:"))
630 (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
631 (add-to-list 'tramp-default-proxies-alist
632 (list (regexp-quote (system-name)) nil nil)))
633
634 (with-eval-after-load 'doc-view
635 (define-key doc-view-mode-map (kbd "M-RET") #'image-previous-line))
636
637 (csetq shr-max-width 80)
638
639 ;; Email (with Gnus, message, and EBDB)
640 (require 'bandali-gnus)
641 (with-eval-after-load 'sendmail
642 (csetq sendmail-program (executable-find "msmtp")
643 ;; message-sendmail-extra-arguments '("-v" "-d")
644 mail-specify-envelope-from t
645 mail-envelope-from 'header))
646 (require 'bandali-message)
647 (require 'bandali-ebdb)
648
649 ;; IRC (with ERC)
650 (require 'bandali-erc)
651
652 ;; 'paste' service (aka scp + web server)
653 (add-to-list 'load-path (b/lisp "scpaste"))
654 (with-eval-after-load 'scpaste
655 (csetq scpaste-http-destination "https://p.bndl.org"
656 scpaste-scp-destination "p:~"))
657 (autoload 'scpaste "scpaste" nil t)
658 (autoload 'scpaste-region "scpaste" nil t)
659 (global-set-key (kbd "C-c a p p") #'scpaste)
660 (global-set-key (kbd "C-c a p r") #'scpaste-region)
661
662 \f
663 ;;; Editing
664
665 ;; display Lisp objects at point in the echo area
666 (when (version< "25" emacs-version)
667 (with-eval-after-load 'eldoc
668 (csetq eldoc-minor-mode-string " eldoc")
669 (global-eldoc-mode)))
670
671 ;; highlight matching parens
672 (require 'paren)
673 (show-paren-mode)
674
675 ;; (require 'elec-pair)
676 ;; (electric-pair-mode)
677
678 (csetq
679 ;; Save what I copy into clipboard from other applications into Emacs'
680 ;; kill-ring, which would allow me to still be able to easily access
681 ;; it in case I kill (cut or copy) something else inside Emacs before
682 ;; yanking (pasting) what I'd originally intended to.
683 save-interprogram-paste-before-kill t)
684 (with-eval-after-load 'simple
685 (column-number-mode 1))
686
687 ;; save minibuffer history
688 (require 'savehist)
689 (csetq savehist-file (b/var "savehist.el"))
690 (savehist-mode)
691 (add-to-list 'savehist-additional-variables 'kill-ring)
692
693 ;; automatically save place in files
694 (when (version< "25" emacs-version)
695 (csetq save-place-file (b/var "save-place.el"))
696 (save-place-mode))
697
698 (defun indicate-buffer-boundaries-left ()
699 (csetq indicate-buffer-boundaries 'left))
700 (with-eval-after-load 'prog-mode
701 (global-prettify-symbols-mode))
702 (add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left)
703
704 (define-key text-mode-map (kbd "C-<return>") #'b/insert-asterism)
705 (add-hook 'text-mode-hook #'indicate-buffer-boundaries-left)
706 (add-hook 'text-mode-hook #'flyspell-mode)
707
708 (add-to-list 'auto-mode-alist '("\\.*rc$" . conf-mode))
709
710 (add-to-list 'auto-mode-alist '("\\.bashrc$" . sh-mode))
711
712 (with-eval-after-load 'flyspell
713 (csetq flyspell-mode-line-string " fly"))
714
715 ;; flycheck
716 ;; (run-with-idle-timer 0.6 nil #'require 'flycheck)
717 ;; (with-eval-after-load 'flycheck
718 ;; (csetq
719 ;; ;; Use the load-path from running Emacs when checking elisp files
720 ;; flycheck-emacs-lisp-load-path 'inherit
721 ;; ;; Only flycheck when I actually save the buffer
722 ;; flycheck-check-syntax-automatically '(mode-enabled save)
723 ;; flycheck-mode-line-prefix "flyc"))
724 ;; (define-key flycheck-mode-map (kbd "M-P") #'flycheck-previous-error)
725 ;; (define-key flycheck-mode-map (kbd "M-N") #'flycheck-next-error)
726 ;; (add-hook 'prog-mode-hook #'flycheck-mode)
727
728 ;; ispell
729 ;; http://endlessparentheses.com/ispell-and-apostrophes.html
730 ;; (run-with-idle-timer 0.6 nil #'require 'ispell)
731 ;; (with-eval-after-load 'ispell
732 ;; ;; ’ can be part of a word
733 ;; (csetq ispell-local-dictionary-alist
734 ;; `((nil "[[:alpha:]]" "[^[:alpha:]]"
735 ;; "['\x2019]" nil ("-B") nil utf-8))
736 ;; ispell-program-name (executable-find "hunspell"))
737 ;; ;; don't send ’ to the subprocess
738 ;; (defun endless/replace-apostrophe (args)
739 ;; (cons (replace-regexp-in-string
740 ;; "’" "'" (car args))
741 ;; (cdr args)))
742 ;; (advice-add #'ispell-send-string :filter-args
743 ;; #'endless/replace-apostrophe)
744 ;; ;; convert ' back to ’ from the subprocess
745 ;; (defun endless/replace-quote (args)
746 ;; (if (not (derived-mode-p 'org-mode))
747 ;; args
748 ;; (cons (replace-regexp-in-string
749 ;; "'" "’" (car args))
750 ;; (cdr args))))
751 ;; (advice-add #'ispell-parse-output :filter-args
752 ;; #'endless/replace-quote))
753
754 ;; abbrev
755 (csetq abbrev-file-name (b/etc "abbrev.el"))
756 (add-hook 'text-mode-hook #'abbrev-mode)
757
758 \f
759 ;;; Programming modes
760
761 (with-eval-after-load 'lisp-mode
762 (defun indent-spaces-mode ()
763 (setq indent-tabs-mode nil))
764 (add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode))
765
766 ;; alloy
767 (add-to-list 'load-path (b/lisp "alloy-mode"))
768 (autoload 'alloy-mode "alloy-mode" nil t)
769 (with-eval-after-load 'alloy-mode
770 (csetq alloy-basic-offset 2)
771 ;; (defun b/alloy-simple-indent (start end)
772 ;; (interactive "r")
773 ;; ;; (if (region-active-p)
774 ;; ;; (indent-rigidly start end alloy-basic-offset)
775 ;; ;; (if (bolp)
776 ;; ;; (indent-rigidly (line-beginning-position)
777 ;; ;; (line-end-position)
778 ;; ;; alloy-basic-offset)))
779 ;; (indent-to (+ (current-column) alloy-basic-offset)))
780 ;; local key bindings
781 (define-key alloy-mode-map (kbd "RET") #'electric-newline-and-maybe-indent)
782 ;; (define-key alloy-mode-map (kbd "TAB") #'b/alloy-simple-indent)
783 (define-key alloy-mode-map (kbd "TAB") #'indent-for-tab-command))
784 (add-to-list 'auto-mode-alist '("\\.\\(als\\|dsh\\)\\'" . alloy-mode))
785 (add-hook 'alloy-mode-hook (lambda nil (setq-local indent-tabs-mode nil)))
786
787 ;; lean
788 ;; (eval-when-compile (defvar lean-mode-map))
789 ;; (run-with-idle-timer 0.4 nil #'require 'lean-mode)
790 ;; (with-eval-after-load 'lean-mode
791 ;; (require 'lean-input)
792 ;; (csetq default-input-method "Lean"
793 ;; lean-input-tweak-all '(lean-input-compose
794 ;; (lean-input-prepend "/")
795 ;; (lean-input-nonempty))
796 ;; lean-input-user-translations '(("/" "/")))
797 ;; (lean-input-setup)
798 ;; ;; local key bindings
799 ;; (define-key lean-mode-map (kbd "S-SPC") #'company-complete))
800
801 (with-eval-after-load 'sgml-mode
802 (csetq sgml-basic-offset 0))
803
804 (with-eval-after-load 'css-mode
805 (csetq css-indent-offset 2))
806
807 ;; po-mode
808 ;; (add-hook 'po-mode-hook (lambda nil (run-with-timer 0.1 nil 'View-exit)))
809
810 ;; auctex
811 ;; (csetq font-latex-fontify-sectioning 'color)
812
813 (with-eval-after-load 'tex-mode
814 (cl-delete-if
815 (lambda (p) (string-match "^---?" (car p)))
816 tex--prettify-symbols-alist))
817 (add-hook 'tex-mode-hook #'auto-fill-mode)
818 (add-hook 'tex-mode-hook #'flyspell-mode)
819
820 \f
821 ;;; Emacs enhancements & auxiliary packages
822
823 (with-eval-after-load 'man
824 (csetq Man-width 80))
825
826 (defun b/*scratch* ()
827 "Switch to `*scratch*' buffer, creating it if it does not exist."
828 (interactive)
829 (switch-to-buffer
830 (or (get-buffer "*scratch*")
831 (with-current-buffer (get-buffer-create "*scratch*")
832 (set-buffer-major-mode (current-buffer))
833 (current-buffer)))))
834 (global-set-key (kbd "C-c s") #'b/*scratch*)
835
836 ;; ,----
837 ;; | make pretty boxed quotes like this
838 ;; `----
839 (add-to-list 'load-path (b/lisp "boxquote"))
840 (run-with-idle-timer 0.6 nil #'require 'boxquote)
841 (with-eval-after-load 'boxquote
842 (defvar b/boxquote-prefix-map)
843 (define-prefix-command 'b/boxquote-prefix-map)
844 (global-set-key (kbd "C-c q") 'b/boxquote-prefix-map)
845 (define-key b/boxquote-prefix-map (kbd "b") #'boxquote-buffer)
846 (define-key b/boxquote-prefix-map (kbd "B") #'boxquote-insert-buffer)
847 (define-key b/boxquote-prefix-map (kbd "d") #'boxquote-defun)
848 (define-key b/boxquote-prefix-map (kbd "F") #'boxquote-insert-file)
849 (define-key b/boxquote-prefix-map (kbd "hf") #'boxquote-describe-function)
850 (define-key b/boxquote-prefix-map (kbd "hk") #'boxquote-describe-key)
851 (define-key b/boxquote-prefix-map (kbd "hv") #'boxquote-describe-variable)
852 (define-key b/boxquote-prefix-map (kbd "hw") #'boxquote-where-is)
853 (define-key b/boxquote-prefix-map (kbd "k") #'boxquote-kill)
854 (define-key b/boxquote-prefix-map (kbd "p") #'boxquote-paragraph)
855 (define-key b/boxquote-prefix-map (kbd "q") #'boxquote-boxquote)
856 (define-key b/boxquote-prefix-map (kbd "r") #'boxquote-region)
857 (define-key b/boxquote-prefix-map (kbd "s") #'boxquote-shell-command)
858 (define-key b/boxquote-prefix-map (kbd "t") #'boxquote-text)
859 (define-key b/boxquote-prefix-map (kbd "T") #'boxquote-title)
860 (define-key b/boxquote-prefix-map (kbd "u") #'boxquote-unbox)
861 (define-key b/boxquote-prefix-map (kbd "U") #'boxquote-unbox-region)
862 (define-key b/boxquote-prefix-map (kbd "y") #'boxquote-yank)
863 (define-key b/boxquote-prefix-map (kbd "M-q") #'boxquote-fill-paragraph)
864 (define-key b/boxquote-prefix-map (kbd "M-w") #'boxquote-kill-ring-save))
865
866 (add-to-list 'load-path (b/lisp "hl-todo"))
867 (run-with-idle-timer 0.5 nil #'require 'hl-todo)
868 (with-eval-after-load 'hl-todo
869 ;; highlight TODOs in buffers
870 (global-hl-todo-mode))
871
872 (add-to-list 'load-path (b/lisp "page-break-lines"))
873 (run-with-idle-timer 0.5 nil #'require 'page-break-lines)
874 (with-eval-after-load 'page-break-lines
875 (csetq page-break-lines-max-width fill-column)
876 (global-page-break-lines-mode))
877
878 ;; expand-region
879 (global-set-key (kbd "C-=") #'er/expand-region)
880
881 (run-with-idle-timer 0.6 nil #'require 'yasnippet)
882 (with-eval-after-load 'yasnippet
883 (declare-function yas-reload-all
884 "yasnippet" (&optional no-jit interactive))
885 (declare-function yas-maybe-expand-abbrev-key-filter
886 "yasnippet" (cmd))
887
888 (defconst yas-verbosity-cur yas-verbosity)
889 (setq yas-verbosity 2)
890 (csetq yas-snippet-dirs `(,(b/etc "yasnippet/snippets")))
891 ;; (add-to-list 'yas-snippet-dirs "~/src/git/guix/etc/snippets" t)
892 (yas-reload-all)
893 (setq yas-verbosity yas-verbosity-cur)
894
895 (defun b/yas-maybe-expand-abbrev-key-filter (cmd)
896 (when (and (yas-maybe-expand-abbrev-key-filter cmd)
897 (not (bound-and-true-p git-commit-mode)))
898 cmd))
899 (defconst b/yas-maybe-expand
900 '(menu-item "" yas-expand
901 :filter b/yas-maybe-expand-abbrev-key-filter))
902 (define-key yas-minor-mode-map (kbd "SPC") b/yas-maybe-expand)
903
904 (yas-global-mode))
905
906 ;; debbugs
907 (global-set-key (kbd "C-c D d") #'debbugs-gnu)
908 (global-set-key (kbd "C-c D b") #'debbugs-gnu-bugs)
909 (global-set-key (kbd "C-c D e") ; bug-gnu-emacs
910 (lambda ()
911 (interactive)
912 (setq debbugs-gnu-current-suppress t)
913 (debbugs-gnu debbugs-gnu-default-severities
914 '("emacs"))))
915 (global-set-key (kbd "C-c D g") ; bug-gnuzilla
916 (lambda ()
917 (interactive)
918 (setq debbugs-gnu-current-suppress t)
919 (debbugs-gnu debbugs-gnu-default-severities
920 '("gnuzilla"))))
921 (global-set-key (kbd "C-c D G b") ; bug-guix
922 (lambda ()
923 (interactive)
924 (setq debbugs-gnu-current-suppress t)
925 (debbugs-gnu debbugs-gnu-default-severities
926 '("guix"))))
927 (global-set-key (kbd "C-c D G p") ; guix-patches
928 (lambda ()
929 (interactive)
930 (setq debbugs-gnu-current-suppress t)
931 (debbugs-gnu debbugs-gnu-default-severities
932 '("guix-patches"))))
933
934 ;; url and url-cache
935 (csetq
936 url-configuration-directory (b/var "url/configuration/")
937 url-cache-directory (b/var "url/cache/"))
938
939 ;; eww
940 (csetq eww-download-directory (file-name-as-directory
941 (getenv "XDG_DOWNLOAD_DIR")))
942 (global-set-key (kbd "C-c a e w") #'eww)
943
944 ;; ;; org-ref
945 ;; (csetq
946 ;; reftex-default-bibliography '("~/usr/org/references.bib")
947 ;; org-ref-default-bibliography '("~/usr/org/references.bib")
948 ;; org-ref-bibliography-notes "~/usr/org/notes.org"
949 ;; org-ref-pdf-directory "~/usr/org/bibtex-pdfs/")
950
951 ;; fill-column-indicator ?
952
953 ;; window
954 (csetq split-width-threshold 150)
955 (global-set-key (kbd "C-c w s l")
956 (lambda ()
957 (interactive)
958 (split-window-right)
959 (other-window 1)))
960 (global-set-key (kbd "C-c w s j")
961 (lambda ()
962 (interactive)
963 (split-window-below)
964 (other-window 1)))
965 (global-set-key (kbd "C-c w q") #'quit-window)
966
967 ;; pass
968 ;; (global-set-key (kbd "C-c a p") #'pass)
969 ;; (add-hook 'pass-mode-hook #'View-exit)
970
971 ;; reftex
972 ;; uncomment to disable reftex-cite's default choice of previous word
973 ;; (with-eval-after-load 'reftex
974 ;; (require 'reftex-cite)
975 ;; (defun reftex-get-bibkey-default ()
976 ;; "If the cursor is in a citation macro, return the word before the macro."
977 ;; (let* ((macro (reftex-what-macro 1)))
978 ;; (save-excursion
979 ;; (when (and macro (string-match "cite" (car macro)))
980 ;; (goto-char (cdr macro)))
981 ;; (reftex-this-word)))))
982 (add-hook 'latex-mode-hook #'reftex-mode)
983
984 ;; dmenu
985 (add-to-list 'load-path (b/lisp "dmenu"))
986 (with-eval-after-load 'dmenu
987 (csetq dmenu-prompt-string "run: "
988 dmenu-save-file (b/var "dmenu-items")))
989 (autoload 'dmenu "dmenu" nil t)
990
991 ;; eosd ?
992
993 ;; delight
994 (run-with-idle-timer 0.5 nil #'require 'delight)
995 (with-eval-after-load 'delight
996 (delight 'auto-fill-function " f" "simple")
997 (delight 'abbrev-mode "" "abbrev")
998 (delight 'page-break-lines-mode "" "page-break-lines")
999 (delight 'mml-mode " mml" "mml")
1000 (delight 'yas-minor-mode "" "yasnippet"))
1001
1002 \f
1003 ;;; Post initialization
1004
1005 (message "Loading %s...done (%.3fs)" user-init-file
1006 (float-time (time-subtract (current-time)
1007 b/before-user-init-time)))
1008
1009 ;;; init.el ends here