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