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