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