* .emacs.d/init.el: No, I don't want a variable-pitched non-monospaced mode-line.
[~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 'fixed-pitch nil :family "Source Code Pro")
467 (set-face-attribute 'fixed-pitch nil :family "Inconsolata")
468 (set-face-attribute 'mode-line nil
469 :background grey ;; :box box
470 :inherit 'fixed-pitch)
471 ;; (set-face-attribute 'mode-line-inactive nil
472 ;; :background darker-grey :box box)
473 )))
474
475 \f
476 ;;; Useful utilities
477
478 (defun b/add-elisp-section ()
479 (interactive)
480 (insert "\n")
481 (forward-line -1)
482 (insert "\n\f\n;;; "))
483
484 (defun b/insert-asterism ()
485 "Insert a centred asterism."
486 (interactive)
487 (let ((asterism "* * *"))
488 (insert
489 (concat
490 "\n"
491 (make-string
492 (floor (/ (- fill-column (length asterism)) 2))
493 ?\s)
494 asterism
495 "\n"))))
496
497 (defun b/start-process (program &rest args)
498 "Same as `start-process', but doesn't bother about name and buffer."
499 (let ((process-name (concat program "_process"))
500 (buffer-name (generate-new-buffer-name
501 (concat program "_output"))))
502 (apply #'start-process
503 process-name buffer-name program args)))
504
505 (defun b/no-mouse-autoselect-window ()
506 "Conveniently disable `focus-follows-mouse'.
507 For disabling the behaviour for certain buffers and/or modes."
508 (make-local-variable 'mouse-autoselect-window)
509 (setq mouse-autoselect-window nil))
510
511 (defun b/kill-current-buffer ()
512 "Kill the current buffer."
513 ;; also see https://redd.it/64xb3q
514 (interactive)
515 (kill-buffer (current-buffer)))
516
517 (defun b/move-indentation-or-beginning-of-line (arg)
518 "Move to the indentation or to the beginning of line."
519 (interactive "^p")
520 ;; (if (bolp)
521 ;; (back-to-indentation)
522 ;; (move-beginning-of-line arg))
523 (if (= (point)
524 (progn (back-to-indentation)
525 (point)))
526 (move-beginning-of-line arg)))
527
528 (defun b/join-line-top ()
529 "Like `join-line', but join next line to the current line."
530 (interactive)
531 (join-line 1))
532
533 (defun b/duplicate-line-or-region (&optional n)
534 "Duplicate the current line, or region (if active).
535 Make N (default: 1) copies of the current line or region."
536 (interactive "*p")
537 (let ((u-r-p (use-region-p)) ; if region is active
538 (n1 (or n 1)))
539 (save-excursion
540 (let ((text
541 (if u-r-p
542 (buffer-substring (region-beginning) (region-end))
543 (prog1 (thing-at-point 'line)
544 (end-of-line)
545 (if (eobp)
546 (newline)
547 (forward-line 1))))))
548 (dotimes (_ (abs n1))
549 (insert text))))))
550
551 \f
552 ;;; General key bindings
553
554 (global-set-key (kbd "C-a") #'b/move-indentation-or-beginning-of-line)
555 (global-set-key (kbd "C-c a i") #'ielm)
556 (global-set-key (kbd "C-c d") #'b/duplicate-line-or-region)
557 (global-set-key (kbd "C-c j") #'b/join-line-top)
558 (global-set-key (kbd "C-S-j") #'b/join-line-top)
559 (global-set-key (kbd "C-c x") #'execute-extended-command)
560
561 ;; evaling and macro-expanding
562 (global-set-key (kbd "C-c e b") #'eval-buffer)
563 (global-set-key (kbd "C-c e e") #'eval-last-sexp)
564 (global-set-key (kbd "C-c e m") #'pp-macroexpand-last-sexp)
565 (global-set-key (kbd "C-c e r") #'eval-region)
566
567 ;; emacs things
568 (global-set-key (kbd "C-c e i") #'emacs-init-time)
569 (global-set-key (kbd "C-c e u") #'emacs-uptime)
570 (global-set-key (kbd "C-c e v") #'emacs-version)
571
572 ;; finding
573 (global-set-key (kbd "C-c f .") #'find-file)
574 (global-set-key (kbd "C-c f d") #'find-name-dired)
575 (global-set-key (kbd "C-c f l") #'find-library)
576 (global-set-key (kbd "C-c f p") #'find-file-at-point)
577
578 ;; frames
579 (global-set-key (kbd "C-c F m") #'make-frame-command)
580 (global-set-key (kbd "C-c F d") #'delete-frame)
581
582 ;; help/describe
583 (global-set-key (kbd "C-S-h F") #'describe-face)
584
585 ;; (global-set-key (kbd "C-x k") #'b/kill-current-buffer)
586 ;; (global-set-key (kbd "C-x K") #'kill-buffer)
587
588 (define-key emacs-lisp-mode-map (kbd "C-<return>") #'b/add-elisp-section)
589
590 (when (display-graphic-p)
591 (global-unset-key (kbd "C-z")))
592
593 \f
594 ;;; Essential packages
595
596 (add-to-list
597 'load-path
598 (expand-file-name
599 (convert-standard-filename "lisp") user-emacs-directory))
600
601 ;; (require 'bandali-exwm)
602
603 (require 'bandali-org)
604
605 ;; (require 'bandali-theme)
606
607 ;; recently opened files
608 (csetq recentf-max-saved-items 2000
609 recentf-save-file (b/var "recentf-save.el"))
610 (run-with-idle-timer 0.2 nil #'require 'recentf)
611 (with-eval-after-load 'recentf
612 ;; (add-to-list 'recentf-keep #'file-remote-p)
613 (recentf-mode)
614
615 (defun b/recentf-open ()
616 "Use `completing-read' to \\[find-file] a recent file."
617 (interactive)
618 (find-file
619 (completing-read "Find recent file: " recentf-list)))
620 (global-set-key (kbd "C-c f r") #'b/recentf-open))
621
622 ;; (fido-mode 1)
623 ;; (defun b/icomplete--fido-mode-setup ()
624 ;; "Customizations to `fido-mode''s minibuffer."
625 ;; (when (and icomplete-mode (icomplete-simple-completing-p))
626 ;; (setq-local
627 ;; ;; icomplete-compute-delay 0.1
628 ;; ;; icomplete-hide-common-prefix t
629 ;; icomplete-separator " · "
630 ;; completion-styles '(basic substring partial-completion flex))))
631 ;; (add-hook 'minibuffer-setup-hook #'b/icomplete--fido-mode-setup 1)
632
633 (require 'bandali-eshell)
634
635 (require 'bandali-ibuffer)
636
637 (require 'bandali-dired)
638
639 (with-eval-after-load 'help
640 (temp-buffer-resize-mode)
641 (csetq help-window-select t))
642
643 (with-eval-after-load 'help-mode
644 ;; local key bindings
645 (define-key help-mode-map (kbd "p") #'backward-button)
646 (define-key help-mode-map (kbd "n") #'forward-button))
647
648 (with-eval-after-load 'tramp
649 (csetq tramp-auto-save-directory (b/var "tramp/auto-save/")
650 tramp-persistency-file-name (b/var "tramp/persistency.el"))
651 (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:"))
652 (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
653 (add-to-list 'tramp-default-proxies-alist
654 (list (regexp-quote (system-name)) nil nil)))
655
656 (with-eval-after-load 'doc-view
657 (define-key doc-view-mode-map (kbd "M-RET") #'image-previous-line))
658
659 (csetq shr-max-width 80)
660
661 ;; Email (with Gnus, message, and smtpmail)
662 (require 'bandali-gnus)
663 (require 'bandali-message)
664 ;; (with-eval-after-load 'smtpmail
665 ;; (csetq smtpmail-queue-mail t
666 ;; smtpmail-queue-dir (concat b/maildir "queue/")))
667
668 ;; IRC (with ERC)
669 (require 'bandali-erc)
670
671 ;; 'paste' service (aka scp + web server)
672 (add-to-list 'load-path (b/lisp "scpaste"))
673 (with-eval-after-load 'scpaste
674 (csetq scpaste-http-destination "https://p.bndl.org"
675 scpaste-scp-destination "p:~"))
676 (autoload 'scpaste "scpaste" nil t)
677 (autoload 'scpaste-region "scpaste" nil t)
678 (global-set-key (kbd "C-c a p p") #'scpaste)
679 (global-set-key (kbd "C-c a p r") #'scpaste-region)
680
681 \f
682 ;;; Editing
683
684 ;; display Lisp objects at point in the echo area
685 (when (version< "25" emacs-version)
686 (with-eval-after-load 'eldoc
687 (csetq eldoc-minor-mode-string " eldoc")
688 (global-eldoc-mode)))
689
690 ;; highlight matching parens
691 (require 'paren)
692 (show-paren-mode)
693
694 ;; (require 'elec-pair)
695 ;; (electric-pair-mode)
696
697 (csetq
698 ;; Save what I copy into clipboard from other applications into Emacs'
699 ;; kill-ring, which would allow me to still be able to easily access
700 ;; it in case I kill (cut or copy) something else inside Emacs before
701 ;; yanking (pasting) what I'd originally intended to.
702 save-interprogram-paste-before-kill t)
703 (with-eval-after-load 'simple
704 (column-number-mode 1)
705 (line-number-mode 1))
706
707 ;; save minibuffer history
708 (require 'savehist)
709 (csetq savehist-file (b/var "savehist.el"))
710 (savehist-mode)
711 (add-to-list 'savehist-additional-variables 'kill-ring)
712
713 ;; automatically save place in files
714 (when (version< "25" emacs-version)
715 (csetq save-place-file (b/var "save-place.el"))
716 (save-place-mode))
717
718 (defun indicate-buffer-boundaries-left ()
719 (csetq indicate-buffer-boundaries 'left))
720 (with-eval-after-load 'prog-mode
721 (global-prettify-symbols-mode))
722 (add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left)
723
724 (define-key text-mode-map (kbd "C-<return>") #'b/insert-asterism)
725 (add-hook 'text-mode-hook #'indicate-buffer-boundaries-left)
726 (add-hook 'text-mode-hook #'flyspell-mode)
727
728 (add-to-list 'auto-mode-alist '("\\.*rc$" . conf-mode))
729
730 (add-to-list 'auto-mode-alist '("\\.bashrc$" . sh-mode))
731
732 (with-eval-after-load 'flyspell
733 (csetq flyspell-mode-line-string " fly"))
734
735 ;; ispell
736 ;; http://endlessparentheses.com/ispell-and-apostrophes.html
737 ;; (run-with-idle-timer 0.6 nil #'require 'ispell)
738 ;; (with-eval-after-load 'ispell
739 ;; ;; ’ can be part of a word
740 ;; (csetq ispell-local-dictionary-alist
741 ;; `((nil "[[:alpha:]]" "[^[:alpha:]]"
742 ;; "['\x2019]" nil ("-B") nil utf-8))
743 ;; ispell-program-name (executable-find "hunspell"))
744 ;; ;; don't send ’ to the subprocess
745 ;; (defun endless/replace-apostrophe (args)
746 ;; (cons (replace-regexp-in-string
747 ;; "’" "'" (car args))
748 ;; (cdr args)))
749 ;; (advice-add #'ispell-send-string :filter-args
750 ;; #'endless/replace-apostrophe)
751 ;; ;; convert ' back to ’ from the subprocess
752 ;; (defun endless/replace-quote (args)
753 ;; (if (not (derived-mode-p 'org-mode))
754 ;; args
755 ;; (cons (replace-regexp-in-string
756 ;; "'" "’" (car args))
757 ;; (cdr args))))
758 ;; (advice-add #'ispell-parse-output :filter-args
759 ;; #'endless/replace-quote))
760
761 ;; abbrev
762 (csetq abbrev-file-name (b/etc "abbrev.el"))
763 (add-hook 'text-mode-hook #'abbrev-mode)
764
765 \f
766 ;;; Programming modes
767
768 (with-eval-after-load 'lisp-mode
769 (defun indent-spaces-mode ()
770 (setq indent-tabs-mode nil))
771 (add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode))
772
773 ;; alloy
774 (add-to-list 'load-path (b/lisp "alloy-mode"))
775 (autoload 'alloy-mode "alloy-mode" nil t)
776 (with-eval-after-load 'alloy-mode
777 (csetq alloy-basic-offset 2)
778 ;; (defun b/alloy-simple-indent (start end)
779 ;; (interactive "r")
780 ;; ;; (if (region-active-p)
781 ;; ;; (indent-rigidly start end alloy-basic-offset)
782 ;; ;; (if (bolp)
783 ;; ;; (indent-rigidly (line-beginning-position)
784 ;; ;; (line-end-position)
785 ;; ;; alloy-basic-offset)))
786 ;; (indent-to (+ (current-column) alloy-basic-offset)))
787 ;; local key bindings
788 (define-key alloy-mode-map (kbd "RET") #'electric-newline-and-maybe-indent)
789 ;; (define-key alloy-mode-map (kbd "TAB") #'b/alloy-simple-indent)
790 (define-key alloy-mode-map (kbd "TAB") #'indent-for-tab-command))
791 (add-to-list 'auto-mode-alist '("\\.\\(als\\|dsh\\)\\'" . alloy-mode))
792 (add-hook 'alloy-mode-hook (lambda nil (setq-local indent-tabs-mode nil)))
793
794 ;; lean
795 ;; (eval-when-compile (defvar lean-mode-map))
796 ;; (run-with-idle-timer 0.4 nil #'require 'lean-mode)
797 ;; (with-eval-after-load 'lean-mode
798 ;; (require 'lean-input)
799 ;; (csetq default-input-method "Lean"
800 ;; lean-input-tweak-all '(lean-input-compose
801 ;; (lean-input-prepend "/")
802 ;; (lean-input-nonempty))
803 ;; lean-input-user-translations '(("/" "/")))
804 ;; (lean-input-setup)
805 ;; ;; local key bindings
806 ;; (define-key lean-mode-map (kbd "S-SPC") #'company-complete))
807
808 (with-eval-after-load 'sgml-mode
809 (csetq sgml-basic-offset 0))
810
811 (with-eval-after-load 'css-mode
812 (csetq css-indent-offset 2))
813
814 ;; auctex
815 ;; (csetq font-latex-fontify-sectioning 'color)
816
817 (with-eval-after-load 'tex-mode
818 (cl-delete-if
819 (lambda (p) (string-match "^---?" (car p)))
820 tex--prettify-symbols-alist))
821 (add-hook 'tex-mode-hook #'auto-fill-mode)
822 (add-hook 'tex-mode-hook #'flyspell-mode)
823
824 \f
825 ;;; Emacs enhancements & auxiliary packages
826
827 (with-eval-after-load 'man
828 (csetq Man-width 80))
829
830 (defun b/*scratch* ()
831 "Switch to `*scratch*' buffer, creating it if it does not exist."
832 (interactive)
833 (switch-to-buffer
834 (or (get-buffer "*scratch*")
835 (with-current-buffer (get-buffer-create "*scratch*")
836 (set-buffer-major-mode (current-buffer))
837 (current-buffer)))))
838 (global-set-key (kbd "C-c s") #'b/*scratch*)
839
840 ;; ,----
841 ;; | make pretty boxed quotes like this
842 ;; `----
843 (add-to-list 'load-path (b/lisp "boxquote"))
844 (run-with-idle-timer 0.6 nil #'require 'boxquote)
845 (with-eval-after-load 'boxquote
846 (defvar b/boxquote-prefix-map)
847 (define-prefix-command 'b/boxquote-prefix-map)
848 (global-set-key (kbd "C-c q") 'b/boxquote-prefix-map)
849 (define-key b/boxquote-prefix-map (kbd "b") #'boxquote-buffer)
850 (define-key b/boxquote-prefix-map (kbd "B") #'boxquote-insert-buffer)
851 (define-key b/boxquote-prefix-map (kbd "d") #'boxquote-defun)
852 (define-key b/boxquote-prefix-map (kbd "F") #'boxquote-insert-file)
853 (define-key b/boxquote-prefix-map (kbd "hf") #'boxquote-describe-function)
854 (define-key b/boxquote-prefix-map (kbd "hk") #'boxquote-describe-key)
855 (define-key b/boxquote-prefix-map (kbd "hv") #'boxquote-describe-variable)
856 (define-key b/boxquote-prefix-map (kbd "hw") #'boxquote-where-is)
857 (define-key b/boxquote-prefix-map (kbd "k") #'boxquote-kill)
858 (define-key b/boxquote-prefix-map (kbd "p") #'boxquote-paragraph)
859 (define-key b/boxquote-prefix-map (kbd "q") #'boxquote-boxquote)
860 (define-key b/boxquote-prefix-map (kbd "r") #'boxquote-region)
861 (define-key b/boxquote-prefix-map (kbd "s") #'boxquote-shell-command)
862 (define-key b/boxquote-prefix-map (kbd "t") #'boxquote-text)
863 (define-key b/boxquote-prefix-map (kbd "T") #'boxquote-title)
864 (define-key b/boxquote-prefix-map (kbd "u") #'boxquote-unbox)
865 (define-key b/boxquote-prefix-map (kbd "U") #'boxquote-unbox-region)
866 (define-key b/boxquote-prefix-map (kbd "y") #'boxquote-yank)
867 (define-key b/boxquote-prefix-map (kbd "M-q") #'boxquote-fill-paragraph)
868 (define-key b/boxquote-prefix-map (kbd "M-w") #'boxquote-kill-ring-save))
869
870 (add-to-list 'load-path (b/lisp "hl-todo"))
871 (run-with-idle-timer 0.5 nil #'require 'hl-todo)
872 (with-eval-after-load 'hl-todo
873 ;; highlight TODOs in buffers
874 (global-hl-todo-mode))
875
876 ;; expand-region
877 (global-set-key (kbd "C-=") #'er/expand-region)
878
879 (run-with-idle-timer 0.6 nil #'require 'yasnippet)
880 (with-eval-after-load 'yasnippet
881 (declare-function yas-reload-all
882 "yasnippet" (&optional no-jit interactive))
883 (declare-function yas-maybe-expand-abbrev-key-filter
884 "yasnippet" (cmd))
885
886 (defconst yas-verbosity-cur yas-verbosity)
887 (setq yas-verbosity 2)
888 (csetq yas-snippet-dirs `(,(b/etc "yasnippet/snippets")))
889 ;; (add-to-list 'yas-snippet-dirs "~/src/git/guix/etc/snippets" t)
890 (yas-reload-all)
891 (setq yas-verbosity yas-verbosity-cur)
892
893 (defun b/yas-maybe-expand-abbrev-key-filter (cmd)
894 (when (and (yas-maybe-expand-abbrev-key-filter cmd)
895 (not (bound-and-true-p git-commit-mode)))
896 cmd))
897 (defconst b/yas-maybe-expand
898 '(menu-item "" yas-expand
899 :filter b/yas-maybe-expand-abbrev-key-filter))
900 (define-key yas-minor-mode-map (kbd "SPC") b/yas-maybe-expand)
901
902 (yas-global-mode))
903
904 ;; debbugs
905 (global-set-key (kbd "C-c D d") #'debbugs-gnu)
906 (global-set-key (kbd "C-c D b") #'debbugs-gnu-bugs)
907 (global-set-key (kbd "C-c D e") ; bug-gnu-emacs
908 (lambda ()
909 (interactive)
910 (setq debbugs-gnu-current-suppress t)
911 (debbugs-gnu debbugs-gnu-default-severities
912 '("emacs"))))
913 (global-set-key (kbd "C-c D g") ; bug-gnuzilla
914 (lambda ()
915 (interactive)
916 (setq debbugs-gnu-current-suppress t)
917 (debbugs-gnu debbugs-gnu-default-severities
918 '("gnuzilla"))))
919
920 ;; url and url-cache
921 (csetq
922 url-configuration-directory (b/var "url/configuration/")
923 url-cache-directory (b/var "url/cache/"))
924
925 ;; eww
926 (csetq eww-download-directory (file-name-as-directory
927 (getenv "XDG_DOWNLOAD_DIR")))
928 (global-set-key (kbd "C-c a e w") #'eww)
929
930 ;; ;; org-ref
931 ;; (csetq
932 ;; reftex-default-bibliography '("~/usr/org/references.bib")
933 ;; org-ref-default-bibliography '("~/usr/org/references.bib")
934 ;; org-ref-bibliography-notes "~/usr/org/notes.org"
935 ;; org-ref-pdf-directory "~/usr/org/bibtex-pdfs/")
936
937 ;; fill-column-indicator ?
938
939 ;; window
940 (csetq split-width-threshold 150)
941 (global-set-key (kbd "C-c w s l")
942 (lambda ()
943 (interactive)
944 (split-window-right)
945 (other-window 1)))
946 (global-set-key (kbd "C-c w s j")
947 (lambda ()
948 (interactive)
949 (split-window-below)
950 (other-window 1)))
951 (global-set-key (kbd "C-c w q") #'quit-window)
952
953 ;; pass
954 ;; (global-set-key (kbd "C-c a p") #'pass)
955 ;; (add-hook 'pass-mode-hook #'View-exit)
956
957 ;; reftex
958 ;; uncomment to disable reftex-cite's default choice of previous word
959 ;; (with-eval-after-load 'reftex
960 ;; (require 'reftex-cite)
961 ;; (defun reftex-get-bibkey-default ()
962 ;; "If the cursor is in a citation macro, return the word before the macro."
963 ;; (let* ((macro (reftex-what-macro 1)))
964 ;; (save-excursion
965 ;; (when (and macro (string-match "cite" (car macro)))
966 ;; (goto-char (cdr macro)))
967 ;; (reftex-this-word)))))
968 (add-hook 'latex-mode-hook #'reftex-mode)
969
970 ;; dmenu
971 (add-to-list 'load-path (b/lisp "dmenu"))
972 (with-eval-after-load 'dmenu
973 (csetq dmenu-prompt-string "run: "
974 dmenu-save-file (b/var "dmenu-items")))
975 (autoload 'dmenu "dmenu" nil t)
976
977 ;; eosd ?
978
979 ;; delight
980 (run-with-idle-timer 0.5 nil #'require 'delight)
981 (with-eval-after-load 'delight
982 (delight 'auto-fill-function " f" "simple")
983 (delight 'abbrev-mode "" "abbrev")
984 (delight 'mml-mode " mml" "mml")
985 (delight 'yas-minor-mode "" "yasnippet"))
986
987 ;; po-mode
988 (require 'bandali-po)
989
990 (with-eval-after-load 'emms
991 (csetq emms-directory (b/var "emms")))
992
993 \f
994 ;;; Post initialization
995
996 (message "Loading %s...done (%.3fs)" user-init-file
997 (float-time (time-subtract (current-time)
998 b/before-user-init-time)))
999
1000 ;;; init.el ends here