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