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