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