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