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