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