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