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