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