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