d7af98c91b4a15dff456b11aa14933941e0359d0
[~bandali/configs] / init.org
1 #+title: =aminb='s Literate Emacs Configuration
2 #+author: Amin Bandali
3 #+babel: :cache yes
4 #+property: header-args :tangle yes
5
6 * About
7 :PROPERTIES:
8 :CUSTOM_ID: about
9 :END:
10
11 This org file is my literate configuration for GNU Emacs, and is
12 tangled to [[./init.el][init.el]]. Packages are installed and managed using
13 [[https://github.com/emacscollective/borg][Borg]]. Over the years, I've taken inspiration from configurations of
14 many different people. Some of the configurations that I can remember
15 off the top of my head are:
16
17 - [[https://github.com/dieggsy/dotfiles][dieggsy/dotfiles]]: literate Emacs and dotfiles configuration, uses
18 straight.el for managing packages
19 - [[https://github.com/dakra/dmacs][dakra/dmacs]]: literate Emacs configuration, using Borg for managing
20 packages
21 - [[http://pages.sachachua.com/.emacs.d/Sacha.html][Sacha Chua's literate Emacs configuration]]
22 - [[https://github.com/dakrone/eos][dakrone/eos]]
23 - Ryan Rix's [[http://doc.rix.si/cce/cce.html][Complete Computing Environment]] ([[http://doc.rix.si/projects/fsem.html][about cce]])
24 - [[https://github.com/jwiegley/dot-emacs][jwiegley/dot-emacs]]: nix-based configuration
25 - [[https://github.com/wasamasa/dotemacs][wasamasa/dotemacs]]
26 - [[https://github.com/hlissner/doom-emacs][Doom Emacs]]
27
28 I'd like to have a fully reproducible Emacs setup (part of the reason
29 why I store my configuration in this repository) but unfortunately out
30 of the box, that's not achievable with =package.el=, not currently
31 anyway. So, I've opted to use Borg. For what it's worth, I briefly
32 experimented with [[https://github.com/raxod502/straight.el][straight.el]], but found that it added about 2 seconds
33 to my init time; which is unacceptable for me: I use Emacs as my
34 window manager (via EXWM) and coming from bspwm, I'm too used to
35 having fast startup times.
36
37 ** Installation
38
39 To use this config for your Emacs, first you need to clone this repo,
40 then bootstrap Borg, tell Borg to retrieve package submodules, and
41 byte-compiled the packages. Something along these lines should work:
42
43 #+begin_src sh :tangle no
44 git clone https://github.com/aminb/dotfiles ~/.emacs.d
45 cd ~/.emacs.d
46 make bootstrap-borg
47 make bootstrap
48 make build
49 #+end_src
50
51 * Contents :toc_1:noexport:
52
53 - [[#about][About]]
54 - [[#header][Header]]
55 - [[#initial-setup][Initial setup]]
56 - [[#core][Core]]
57 - [[#post-initialization][Post initialization]]
58 - [[#footer][Footer]]
59
60 * Header
61 :PROPERTIES:
62 :CUSTOM_ID: header
63 :END:
64
65 ** First line
66
67 #+begin_src emacs-lisp :comments none
68 ;;; init.el --- Amin Bandali's Emacs config -*- lexical-binding: t; eval: (view-mode 1) -*-
69 #+end_src
70
71 Enable =view-mode=, which both makes the file read-only (as a reminder
72 that =init.el= is an auto-generated file, not supposed to be edited),
73 and provides some convenient key bindings for browsing through the
74 file.
75
76 ** License
77
78 #+begin_src emacs-lisp :comments none
79 ;; Copyright (C) 2018 Amin Bandali <bandali@gnu.org>
80
81 ;; This program is free software: you can redistribute it and/or modify
82 ;; it under the terms of the GNU General Public License as published by
83 ;; the Free Software Foundation, either version 3 of the License, or
84 ;; (at your option) any later version.
85
86 ;; This program is distributed in the hope that it will be useful,
87 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
88 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
89 ;; GNU General Public License for more details.
90
91 ;; You should have received a copy of the GNU General Public License
92 ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
93 #+end_src
94
95 ** Commentary
96
97 #+begin_src emacs-lisp :comments none
98 ;;; Commentary:
99
100 ;; Emacs configuration of Amin Bandali, computer scientist and functional
101 ;; programmer.
102
103 ;; THIS FILE IS AUTO-GENERATED FROM `init.org'.
104 #+end_src
105
106 ** Naming conventions
107
108 The conventions below were inspired by [[https://github.com/hlissner/doom-emacs][Doom]]'s, found [[https://github.com/hlissner/doom-emacs/blob/5dacbb7cb1c6ac246a9ccd15e6c4290def67757c/core/core.el#L3-L17][here]].
109
110 #+begin_src emacs-lisp :comments none
111 ;; Naming conventions:
112 ;;
113 ;; amin-... public variables or non-interactive functions
114 ;; amin--... private anything (non-interactive), not safe for direct use
115 ;; amin/... an interactive function; safe for M-x or keybinding
116 ;; amin|... a hook function
117 ;; amin*... an advising function
118 ;; amin@... a hydra command
119 ;; ...! a macro
120 #+end_src
121
122 * Initial setup
123 :PROPERTIES:
124 :CUSTOM_ID: initial-setup
125 :END:
126
127 ** Emacs initialization
128
129 I'd like to do a couple of measurements of Emacs' startup time. First,
130 let's see how long Emacs takes to start up, before even loading
131 =init.el=, i.e. =user-init-file=:
132
133 #+begin_src emacs-lisp
134 (defvar amin--before-user-init-time (current-time)
135 "Value of `current-time' when Emacs begins loading `user-init-file'.")
136 (message "Loading Emacs...done (%.3fs)"
137 (float-time (time-subtract amin--before-user-init-time
138 before-init-time)))
139 #+end_src
140
141 Also, temporarily increase ~gc-cons-threshhold~ and
142 ~gc-cons-percentage~ during startup to reduce garbage collection
143 frequency. Clearing the ~file-name-handler-alist~ seems to help reduce
144 startup time as well.
145
146 #+begin_src emacs-lisp
147 (defvar amin--gc-cons-threshold gc-cons-threshold)
148 (defvar amin--gc-cons-percentage gc-cons-percentage)
149 (defvar amin--file-name-handler-alist file-name-handler-alist)
150 (setq gc-cons-threshold (* 400 1024 1024) ; 400 MiB
151 gc-cons-percentage 0.6
152 file-name-handler-alist nil
153 ;; sidesteps a bug when profiling with esup
154 esup-child-profile-require-level 0)
155 #+end_src
156
157 Of course, we'd like to set them back to their defaults once we're
158 done initializing.
159
160 #+begin_src emacs-lisp
161 (add-hook
162 'after-init-hook
163 (lambda ()
164 (setq gc-cons-threshold amin--gc-cons-threshold
165 gc-cons-percentage amin--gc-cons-percentage
166 file-name-handler-alist amin--file-name-handler-alist)))
167 #+end_src
168
169 Increase the number of lines kept in message logs (the =*Messages*=
170 buffer).
171
172 #+begin_src emacs-lisp
173 (setq message-log-max 20000)
174 #+end_src
175
176 Optionally, we could suppress some byte compiler warnings like below,
177 but for now I've decided to keep them enabled. See documentation for
178 ~byte-compile-warnings~ for more details.
179
180 #+begin_src emacs-lisp
181 ;; (setq byte-compile-warnings
182 ;; '(not free-vars unresolved noruntime lexical make-local))
183 #+end_src
184
185 ** whoami
186
187 #+begin_src emacs-lisp
188 (setq user-full-name "Amin Bandali"
189 user-mail-address "amin@aminb.org")
190 #+end_src
191
192 ** Package management
193
194 *** No =package.el=
195
196 I can do all my package management things with Borg, and don't need
197 Emacs' built-in =package.el=. Emacs 27 lets us disable =package.el= in
198 the =early-init-file= (see [[https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=24acb31c04b4048b85311d794e600ecd7ce60d3b][here]]).
199
200 #+begin_src emacs-lisp :tangle early-init.el
201 (setq package-enable-at-startup nil)
202 #+end_src
203
204 But since Emacs 27 isn't out yet (Emacs 26 is just around the corner
205 right now), and even when released it'll be long before most distros
206 ship in their repos, I'll still put the old workaround with the
207 commented call to ~package-initialize~ here anyway.
208
209 #+begin_src emacs-lisp
210 (setq package-enable-at-startup nil)
211 ;; (package-initialize)
212 #+end_src
213
214 *** Borg
215
216 #+begin_quote
217 Assimilate Emacs packages as Git submodules
218 #+end_quote
219
220 [[https://github.com/emacscollective/borg][Borg]] is at the heart of package management of my Emacs setup. In
221 short, it creates a git submodule in =lib/= for each package, which
222 can then be managed with the help of Magit or other tools.
223
224 #+begin_src emacs-lisp
225 (setq user-init-file (or load-file-name buffer-file-name)
226 user-emacs-directory (file-name-directory user-init-file))
227 (add-to-list 'load-path
228 (expand-file-name "lib/borg" user-emacs-directory))
229 (require 'borg)
230 (borg-initialize)
231
232 ;; (require 'borg-nix-shell)
233 ;; (setq borg-build-shell-command 'borg-nix-shell-build-command)
234
235 (with-eval-after-load 'bind-key
236 (bind-keys
237 :package borg
238 ("C-c b A" . borg-activate)
239 ("C-c b a" . borg-assimilate)
240 ("C-c b b" . borg-build)
241 ("C-c b c" . borg-clone)
242 ("C-c b r" . borg-remove)))
243 #+end_src
244
245 *** =use-package=
246
247 #+begin_quote
248 A use-package declaration for simplifying your .emacs
249 #+end_quote
250
251 [[https://github.com/jwiegley/use-package][use-package]] is an awesome utility for managing and configuring
252 packages (in our case especially the latter) in a neatly organized way
253 and without compromising on performance.
254
255 #+begin_src emacs-lisp
256 (require 'use-package)
257 (if nil ; set to t when need to debug init
258 (setq use-package-verbose t
259 use-package-expand-minimally nil
260 use-package-compute-statistics t
261 debug-on-error t)
262 (setq use-package-verbose nil
263 use-package-expand-minimally t))
264 #+end_src
265
266 *** Epkg
267
268 #+begin_quote
269 Browse the Emacsmirror package database
270 #+end_quote
271
272 Epkg provides access to a local copy of the [[https://emacsmirror.net][Emacsmirror]] package
273 database, low-level functions for querying the database, and a
274 =package.el=-like user interface for browsing the available packages.
275
276 #+begin_src emacs-lisp
277 (use-package epkg
278 :defer t
279 :bind
280 (("C-c b d" . epkg-describe-package)
281 ("C-c b p" . epkg-list-packages)
282 ("C-c b u" . epkg-update)))
283 #+end_src
284
285 ** No littering in =~/.emacs.d=
286
287 #+begin_quote
288 Help keeping ~/.emacs.d clean
289 #+end_quote
290
291 By default, even for Emacs' built-in packages, the configuration files
292 and persistent data are all over the place. Use =no-littering= to help
293 contain the mess.
294
295 #+begin_src emacs-lisp
296 (use-package no-littering
297 :demand t
298 :config
299 (savehist-mode 1)
300 (add-to-list 'savehist-additional-variables 'kill-ring)
301 (save-place-mode 1)
302 (setq auto-save-file-name-transforms
303 `((".*" ,(no-littering-expand-var-file-name "auto-save/") t))))
304 #+end_src
305
306 ** Custom file (=custom.el=)
307
308 I'm not planning on using the custom file much, but even so, I
309 definitely don't want it mixing with =init.el=. So, here; let's give
310 it it's own file. While at it, treat themes as safe.
311
312 #+begin_src emacs-lisp
313 (use-package custom
314 :no-require t
315 :config
316 (setq custom-file (no-littering-expand-etc-file-name "custom.el"))
317 (when (file-exists-p custom-file)
318 (load custom-file))
319 (setf custom-safe-themes t))
320 #+end_src
321
322 ** Secrets file
323
324 Load the secrets file if it exists, otherwise show a warning.
325
326 #+begin_src emacs-lisp
327 (with-demoted-errors
328 (load (no-littering-expand-etc-file-name "secrets")))
329 #+end_src
330
331 ** Better =$PATH= handling
332
333 Let's use [[https://github.com/purcell/exec-path-from-shell][exec-path-from-shell]] to make Emacs use the =$PATH= as set up
334 in my shell.
335
336 #+begin_src emacs-lisp
337 (use-package exec-path-from-shell
338 :defer 1
339 :init
340 (setq exec-path-from-shell-check-startup-files nil)
341 :config
342 (exec-path-from-shell-initialize)
343 ;; while we're at it, let's fix access to our running ssh-agent
344 (exec-path-from-shell-copy-env "SSH_AGENT_PID")
345 (exec-path-from-shell-copy-env "SSH_AUTH_SOCK"))
346 #+end_src
347
348 ** Only one custom theme at a time
349
350 #+begin_src emacs-lisp
351 ;; only one custom theme at a time
352 ;;
353 ;; (defadvice load-theme (before clear-previous-themes activate)
354 ;; "Clear existing theme settings instead of layering them"
355 ;; (mapc #'disable-theme custom-enabled-themes))
356 #+end_src
357
358 ** Server
359
360 Start server if not already running. Alternatively, can be done by
361 issuing =emacs --daemon= in the terminal, which can be automated with
362 a systemd service or using =brew services start emacs= on macOS. I use
363 Emacs as my window manager (via EXWM), so I always start Emacs on
364 login; so starting the server from inside Emacs is good enough for me.
365
366 See [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html#Emacs-Server][Using Emacs as a Server]].
367
368 #+begin_src emacs-lisp
369 (use-package server
370 :defer 1
371 :config (or (server-running-p) (server-mode)))
372 #+end_src
373
374 ** Unicode support
375
376 Font stack with better unicode support, around =Ubuntu Mono= and
377 =Hack=.
378
379 #+begin_src emacs-lisp
380 ;; unicode support
381 ;;
382 ;; (dolist (ft (fontset-list))
383 ;; (set-fontset-font
384 ;; ft
385 ;; 'unicode
386 ;; (font-spec :name "Source Code Pro" :size 14))
387 ;; (set-fontset-font
388 ;; ft
389 ;; 'unicode
390 ;; (font-spec :name "DejaVu Sans Mono")
391 ;; nil
392 ;; 'append)
393 ;; ;; (set-fontset-font
394 ;; ;; ft
395 ;; ;; 'unicode
396 ;; ;; (font-spec
397 ;; ;; :name "Symbola monospacified for DejaVu Sans Mono")
398 ;; ;; nil
399 ;; ;; 'append)
400 ;; ;; (set-fontset-font
401 ;; ;; ft
402 ;; ;; #x2115 ; ℕ
403 ;; ;; (font-spec :name "DejaVu Sans Mono")
404 ;; ;; nil
405 ;; ;; 'append)
406 ;; (set-fontset-font
407 ;; ft
408 ;; (cons ?Α ?ω)
409 ;; (font-spec :name "DejaVu Sans Mono" :size 14)
410 ;; nil
411 ;; 'prepend))
412 #+end_src
413
414 ** Gentler font resizing
415
416 #+begin_src emacs-lisp
417 (setq text-scale-mode-step 1.05)
418 #+end_src
419
420 ** Focus follows mouse
421
422 I’d like focus to follow the mouse when I move the cursor from one
423 window to the next.
424
425 #+begin_src emacs-lisp
426 (setq mouse-autoselect-window t)
427 #+end_src
428
429 Let’s define a function to conveniently disable this for certain
430 buffers and/or modes.
431
432 #+begin_src emacs-lisp
433 (defun amin--no-mouse-autoselect-window ()
434 (make-local-variable 'mouse-autoselect-window)
435 (setq mouse-autoselect-window nil))
436 #+end_src
437
438 ** Libraries
439
440 #+begin_src emacs-lisp
441 (require 'cl-lib)
442 (require 'subr-x)
443 #+end_src
444
445 ** Useful utilities
446
447 #+begin_src emacs-lisp
448 (defun amin-enlist (exp)
449 "Return EXP wrapped in a list, or as-is if already a list."
450 (if (listp exp) exp (list exp)))
451
452 ; from https://github.com/hlissner/doom-emacs/commit/589108fdb270f24a98ba6209f6955fe41530b3ef
453 (defmacro after! (features &rest body)
454 "A smart wrapper around `with-eval-after-load'. Supresses warnings during
455 compilation."
456 (declare (indent defun) (debug t))
457 (list (if (or (not (bound-and-true-p byte-compile-current-file))
458 (dolist (next (amin-enlist features))
459 (if (symbolp next)
460 (require next nil :no-error)
461 (load next :no-message :no-error))))
462 #'progn
463 #'with-no-warnings)
464 (cond ((symbolp features)
465 `(eval-after-load ',features '(progn ,@body)))
466 ((and (consp features)
467 (memq (car features) '(:or :any)))
468 `(progn
469 ,@(cl-loop for next in (cdr features)
470 collect `(after! ,next ,@body))))
471 ((and (consp features)
472 (memq (car features) '(:and :all)))
473 (dolist (next (cdr features))
474 (setq body `(after! ,next ,@body)))
475 body)
476 ((listp features)
477 `(after! (:all ,@features) ,@body)))))
478 #+end_src
479
480 Convenience macro for =setq='ing multiple variables to the same value:
481
482 #+begin_src emacs-lisp
483 (defmacro setq-every! (value &rest vars)
484 "Set all the variables from VARS to value VALUE."
485 (declare (indent defun) (debug t))
486 `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars)))
487 #+end_src
488
489 * Core
490 :PROPERTIES:
491 :CUSTOM_ID: core
492 :END:
493
494 ** Defaults
495
496 *** Time and battery in mode-line
497
498 Enable displaying time and battery in the mode-line, since I'm not
499 using the Xfce panel anymore. Also, I don't need to see the load
500 average on a regular basis, so disable that.
501
502 Note: using =i3status= on sway at the moment, so disabling this.
503
504 #+begin_src emacs-lisp :tangle no
505 (use-package time
506 :init
507 (setq display-time-default-load-average nil)
508 :config
509 (display-time-mode))
510
511 (use-package battery
512 :config
513 (display-battery-mode))
514 #+end_src
515
516 *** Smaller fringe
517
518 Might want to set the fringe to a smaller value, especially if using
519 EXWM. I'm fine with the default for now.
520
521 #+begin_src emacs-lisp
522 ;; (fringe-mode '(3 . 1))
523 (fringe-mode nil)
524 #+end_src
525
526 *** Disable disabled commands
527
528 Emacs disables some commands by default that could persumably be
529 confusing for novice users. Let's disable that.
530
531 #+begin_src emacs-lisp
532 (setq disabled-command-function nil)
533 #+end_src
534
535 *** Kill-ring
536
537 Save what I copy into clipboard from other applications into Emacs'
538 kill-ring, which would allow me to still be able to easily access it
539 in case I kill (cut or copy) something else inside Emacs before
540 yanking (pasting) what I'd originally intended to.
541
542 #+begin_src emacs-lisp
543 (setq save-interprogram-paste-before-kill t)
544 #+end_src
545
546 *** Minibuffer
547
548 #+begin_src emacs-lisp
549 (setq enable-recursive-minibuffers t
550 resize-mini-windows t)
551 #+end_src
552
553 *** Lazy-person-friendly yes/no prompts
554
555 Lazy people would prefer to type fewer keystrokes, especially for yes
556 or no questions. I'm lazy.
557
558 #+begin_src emacs-lisp
559 (defalias 'yes-or-no-p #'y-or-n-p)
560 #+end_src
561
562 *** Startup screen and =*scratch*=
563
564 Firstly, let Emacs know that I'd like to have =*scratch*= as my
565 startup buffer.
566
567 #+begin_src emacs-lisp
568 (setq initial-buffer-choice t)
569 #+end_src
570
571 Now let's customize the =*scratch*= buffer a bit. First off, I don't
572 need the default hint.
573
574 #+begin_src emacs-lisp
575 (setq initial-scratch-message nil)
576 #+end_src
577
578 Also, let's use Text mode as the major mode, in case I want to
579 customize it (=*scratch*='s default major mode, Fundamental mode,
580 can't really be customized).
581
582 #+begin_src emacs-lisp
583 (setq initial-major-mode 'text-mode)
584 #+end_src
585
586 Inhibit the buffer list when more than 2 files are loaded.
587
588 #+begin_src emacs-lisp
589 (setq inhibit-startup-buffer-menu t)
590 #+end_src
591
592 I don't really need to see the startup screen or echo area message
593 either.
594
595 #+begin_src emacs-lisp
596 (advice-add #'display-startup-echo-area-message :override #'ignore)
597 (setq inhibit-startup-screen t
598 inhibit-startup-echo-area-message user-login-name)
599 #+end_src
600
601 *** More useful frame titles
602
603 Show either the file name or the buffer name (in case the buffer isn't
604 visiting a file). Borrowed from Emacs Prelude.
605
606 #+begin_src emacs-lisp
607 (setq frame-title-format
608 '("" invocation-name " - "
609 (:eval (if (buffer-file-name)
610 (abbreviate-file-name (buffer-file-name))
611 "%b"))))
612 #+end_src
613
614 *** Backups
615
616 Emacs' default backup settings aren't that great. Let's use more
617 sensible options. See documentation for the ~make-backup-file~
618 variable.
619
620 #+begin_src emacs-lisp
621 (setq backup-by-copying t
622 version-control t
623 delete-old-versions t)
624 #+end_src
625
626 *** Auto revert
627
628 Enable automatic reloading of changed buffers and files.
629
630 #+begin_src emacs-lisp
631 (global-auto-revert-mode 1)
632 (setq auto-revert-verbose nil
633 global-auto-revert-non-file-buffers nil)
634 #+end_src
635
636 *** Always use space for indentation
637
638 #+begin_src emacs-lisp
639 (setq-default
640 indent-tabs-mode nil
641 require-final-newline t
642 tab-width 4)
643 #+end_src
644
645 *** Winner mode
646
647 Enable =winner-mode=.
648
649 #+begin_src emacs-lisp
650 (winner-mode 1)
651 #+end_src
652
653 *** Don’t display =*compilation*= on success
654
655 From https://stackoverflow.com/a/17788551.
656
657 #+begin_src emacs-lisp
658 (defun amin--compilation-finish-function (buffer outstr)
659 (unless (string-match "finished" outstr)
660 (switch-to-buffer-other-window buffer))
661 t)
662
663 (setq compilation-finish-functions #'amin--compilation-finish-function)
664
665 (require 'cl)
666
667 (defadvice compilation-start
668 (around inhibit-display
669 (command &optional mode name-function highlight-regexp))
670 (if (not (string-match "^\\(find\\|grep\\)" command))
671 (flet ((display-buffer)
672 (set-window-point)
673 (goto-char))
674 (fset 'display-buffer 'ignore)
675 (fset 'goto-char 'ignore)
676 (fset 'set-window-point 'ignore)
677 (save-window-excursion
678 ad-do-it))
679 ad-do-it))
680
681 (ad-activate 'compilation-start)
682 #+end_src
683
684 *** Search for non-ASCII characters
685
686 I’d like non-ASCII characters such as ‘’“”«»‹›áⓐ𝒶 to be selected when
687 I search for their ASCII counterpart. Shoutout to [[http://endlessparentheses.com/new-in-emacs-25-1-easily-search-non-ascii-characters.html][endlessparentheses]]
688 for this.
689
690 #+begin_src emacs-lisp
691 (setq search-default-mode #'char-fold-to-regexp)
692
693 ;; uncomment to extend this behaviour to query-replace
694 ;; (setq replace-char-fold t)
695 #+end_src
696
697 ** Bindings
698
699 #+begin_src emacs-lisp
700 (bind-keys
701 ("C-c a i" . ielm)
702
703 ("C-c e b" . eval-buffer)
704 ("C-c e r" . eval-region)
705
706 ("C-c F m" . make-frame-command)
707 ("C-c F d" . delete-frame)
708 ("C-c F D" . delete-other-frames)
709
710 ("C-c o" . other-window)
711
712 ("C-c Q" . save-buffers-kill-terminal)
713
714 ("C-S-h C" . describe-char)
715 ("C-S-h F" . describe-face)
716
717 ("C-x K" . kill-this-buffer)
718
719 ("s-p" . beginning-of-buffer)
720 ("s-n" . end-of-buffer))
721 #+end_src
722
723 ** Packages
724
725 The packages in this section are absolutely essential to my everyday
726 workflow, and they play key roles in how I do my computing. They
727 immensely enhance the Emacs experience for me; both using Emacs, and
728 customizing it.
729
730 *** [[https://github.com/emacscollective/auto-compile][auto-compile]]
731
732 #+begin_src emacs-lisp
733 (use-package auto-compile
734 :demand t
735 :config
736 (auto-compile-on-load-mode)
737 (auto-compile-on-save-mode)
738 (setq auto-compile-display-buffer nil
739 auto-compile-mode-line-counter t
740 auto-compile-source-recreate-deletes-dest t
741 auto-compile-toggle-deletes-nonlib-dest t
742 auto-compile-update-autoloads t)
743 (add-hook 'auto-compile-inhibit-compile-hook
744 'auto-compile-inhibit-compile-detached-git-head))
745 #+end_src
746
747 *** [[https://orgmode.org/][Org mode]]
748
749 #+begin_quote
750 Org mode is for keeping notes, maintaining TODO lists, planning
751 projects, and authoring documents with a fast and effective plain-text
752 system.
753 #+end_quote
754
755 In short, my favourite way of life.
756
757 #+begin_src emacs-lisp
758 (use-package org
759 :defer 1
760 :config
761 (setq org-src-tab-acts-natively t
762 org-src-preserve-indentation nil
763 org-edit-src-content-indentation 0
764 org-email-link-description-format "Email %c: %s" ; %.30s
765 org-highlight-latex-and-related '(entities)
766 org-log-done 'time)
767 (add-to-list 'org-structure-template-alist '("L" . "src emacs-lisp") t)
768 (font-lock-add-keywords
769 'org-mode
770 '(("[ \t]*\\(#\\+\\(BEGIN\\|END\\|begin\\|end\\)_\\(\\S-+\\)\\)[ \t]*\\([^\n:]*\\)"
771 (1 '(:foreground "#5a5b5a" :background "#292b2b") t) ; directive
772 (3 '(:foreground "#81a2be" :background "#292b2b") t) ; kind
773 (4 '(:foreground "#c5c8c6") t))) ; title
774 t)
775 :bind (:map org-mode-map ("M-L" . org-insert-last-stored-link))
776 :hook ((org-mode . org-indent-mode)
777 (org-mode . auto-fill-mode)
778 (org-mode . flyspell-mode))
779 :custom
780 (org-latex-packages-alist '(("" "listings") ("" "color")))
781 :custom-face
782 '(org-block-begin-line ((t (:foreground "#5a5b5a" :background "#1d1f21"))))
783 '(org-block ((t (:background "#1d1f21"))))
784 '(org-latex-and-related ((t (:foreground "#b294bb")))))
785
786 (use-package ox-latex
787 :after ox
788 :config
789 (setq org-latex-listings 'listings
790 ;; org-latex-prefer-user-labels t
791 )
792 (add-to-list 'org-latex-packages-alist '("" "listings"))
793 (add-to-list 'org-latex-packages-alist '("" "color"))
794 (add-to-list 'org-latex-classes
795 '("IEEEtran" "\\documentclass[11pt]{IEEEtran}"
796 ("\\section{%s}" . "\\section*{%s}")
797 ("\\subsection{%s}" . "\\subsection*{%s}")
798 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
799 ("\\paragraph{%s}" . "\\paragraph*{%s}")
800 ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
801 t))
802
803 (use-package ox-beamer
804 :after ox)
805
806 (use-package orgalist
807 :after message
808 :hook (message-mode . orgalist-mode))
809 #+end_src
810
811 **** asynchronous tangle
812
813 =amin/async-babel-tangle= is a function closely inspired by [[https://github.com/dieggsy/dotfiles/tree/cc10edf7701958eff1cd94d4081da544d882a28c/emacs.d#dotfiles][dieggsy's
814 d/async-babel-tangle]] which uses [[https://github.com/jwiegley/emacs-async][async]] to asynchronously tangle an org
815 file.
816
817 #+begin_src emacs-lisp
818 (after! org
819 (defvar amin-show-async-tangle-results nil
820 "Keep *emacs* async buffers around for later inspection.")
821
822 (defvar amin-show-async-tangle-time nil
823 "Show the time spent tangling the file.")
824
825 (defvar amin-async-tangle-post-compile "make ti"
826 "If non-nil, pass to `compile' after successful tangle.")
827
828 (defun amin/async-babel-tangle ()
829 "Tangle org file asynchronously."
830 (interactive)
831 (let* ((file-tangle-start-time (current-time))
832 (file (buffer-file-name))
833 (file-nodir (file-name-nondirectory file))
834 (async-quiet-switch "-q"))
835 (async-start
836 `(lambda ()
837 (require 'org)
838 (org-babel-tangle-file ,file))
839 (unless amin-show-async-tangle-results
840 `(lambda (result)
841 (if result
842 (progn
843 (message "Tangled %s%s"
844 ,file-nodir
845 (if amin-show-async-tangle-time
846 (format " (%.3fs)"
847 (float-time (time-subtract (current-time)
848 ',file-tangle-start-time)))
849 ""))
850 (when amin-async-tangle-post-compile
851 (compile amin-async-tangle-post-compile)))
852 (message "Tangling %s failed" ,file-nodir))))))))
853
854 (add-to-list
855 'safe-local-variable-values
856 '(eval add-hook 'after-save-hook #'amin/async-babel-tangle 'append 'local))
857 #+end_src
858
859 *** [[https://magit.vc/][Magit]]
860
861 #+begin_quote
862 It's Magit! A Git porcelain inside Emacs.
863 #+end_quote
864
865 Not just how I do git, but /the/ way to do git.
866
867 #+begin_src emacs-lisp
868 (use-package magit
869 :defer 1
870 :bind (("C-x g" . magit-status)
871 ("s-g s" . magit-status)
872 ("s-g l" . magit-log-buffer-file))
873 :config
874 (magit-add-section-hook 'magit-status-sections-hook
875 'magit-insert-modules
876 'magit-insert-stashes
877 'append)
878 (setq
879 magit-repository-directories '(("~/.emacs.d/" . 0)
880 ("~/src/git/" . 1)))
881 (nconc magit-section-initial-visibility-alist
882 '(([unpulled status] . show)
883 ([unpushed status] . show)))
884 :custom-face (magit-diff-file-heading ((t (:weight normal)))))
885 #+end_src
886
887 *** [[https://github.com/abo-abo/swiper][Ivy]] (and friends)
888
889 #+begin_quote
890 Ivy - a generic completion frontend for Emacs, Swiper - isearch with
891 an overview, and more. Oh, man!
892 #+end_quote
893
894 There's no way I could top that, so I won't attempt to.
895
896 **** Ivy
897
898 #+begin_src emacs-lisp
899 (use-package ivy
900 :defer 1
901 :bind
902 (:map ivy-minibuffer-map
903 ([escape] . keyboard-escape-quit)
904 ([S-up] . ivy-previous-history-element)
905 ([S-down] . ivy-next-history-element)
906 ("DEL" . ivy-backward-delete-char))
907 :config
908 (setq ivy-wrap t)
909 (ivy-mode 1)
910 ;; :custom-face
911 ;; (ivy-minibuffer-match-face-2 ((t (:background "#e99ce8" :weight semi-bold))))
912 ;; (ivy-minibuffer-match-face-3 ((t (:background "#bbbbff" :weight semi-bold))))
913 ;; (ivy-minibuffer-match-face-4 ((t (:background "#ffbbff" :weight semi-bold))))
914 )
915 #+end_src
916
917 **** Swiper
918
919 #+begin_src emacs-lisp
920 (use-package swiper
921 :bind (("C-s" . swiper)
922 ("C-r" . swiper)))
923 #+end_src
924
925 **** Counsel
926
927 #+begin_src emacs-lisp
928 (use-package counsel
929 :defer 1
930 :bind (([remap execute-extended-command] . counsel-M-x)
931 ([remap find-file] . counsel-find-file)
932 ("s-r" . counsel-recentf)
933 ("C-c x" . counsel-M-x)
934 ("C-c f ." . counsel-find-file)
935 :map minibuffer-local-map
936 ("C-r" . counsel-minibuffer-history))
937 :config
938 (counsel-mode 1)
939 (defalias 'locate #'counsel-locate))
940 #+end_src
941
942 *** eshell
943
944 #+begin_src emacs-lisp
945 (use-package eshell
946 :defer 1
947 :commands eshell
948 :config
949 (eval-when-compile (defvar eshell-prompt-regexp))
950 (defun amin/eshell-quit-or-delete-char (arg)
951 (interactive "p")
952 (if (and (eolp) (looking-back eshell-prompt-regexp nil))
953 (eshell-life-is-too-much)
954 (delete-char arg)))
955
956 (defun amin/eshell-clear ()
957 (interactive)
958 (let ((inhibit-read-only t))
959 (erase-buffer))
960 (eshell-send-input))
961
962 (defun amin|eshell-setup ()
963 (make-local-variable 'company-idle-delay)
964 (setq company-idle-delay nil)
965 (bind-keys :map eshell-mode-map
966 ("C-d" . amin/eshell-quit-or-delete-char)
967 ("C-S-l" . amin/eshell-clear)
968 ("M-r" . counsel-esh-history)
969 ([tab] . company-complete)))
970
971 :hook (eshell-mode . amin|eshell-setup)
972 :custom
973 (eshell-hist-ignoredups t)
974 (eshell-input-filter 'eshell-input-filter-initial-space))
975 #+end_src
976
977 *** Ibuffer
978
979 #+begin_src emacs-lisp
980 (use-package ibuffer
981 :defer t
982 :bind
983 (("C-x C-b" . ibuffer-other-window)
984 :map ibuffer-mode-map
985 ("P" . ibuffer-backward-filter-group)
986 ("N" . ibuffer-forward-filter-group)
987 ("M-p" . ibuffer-do-print)
988 ("M-n" . ibuffer-do-shell-command-pipe-replace))
989 :config
990 ;; Use human readable Size column instead of original one
991 (define-ibuffer-column size-h
992 (:name "Size" :inline t)
993 (cond
994 ((> (buffer-size) 1000000) (format "%7.1fM" (/ (buffer-size) 1000000.0)))
995 ((> (buffer-size) 100000) (format "%7.0fk" (/ (buffer-size) 1000.0)))
996 ((> (buffer-size) 1000) (format "%7.1fk" (/ (buffer-size) 1000.0)))
997 (t (format "%8d" (buffer-size)))))
998 :custom
999 (ibuffer-saved-filter-groups
1000 '(("default"
1001 ("dired" (mode . dired-mode))
1002 ("org" (mode . org-mode))
1003 ("web"
1004 (or
1005 (mode . web-mode)
1006 (mode . css-mode)
1007 (mode . scss-mode)
1008 (mode . js2-mode)))
1009 ("shell"
1010 (or
1011 (mode . eshell-mode)
1012 (mode . shell-mode)))
1013 ("notmuch" (name . "\*notmuch\*"))
1014 ("programming"
1015 (or
1016 (mode . python-mode)
1017 (mode . c++-mode)
1018 (mode . emacs-lisp-mode)))
1019 ("emacs"
1020 (or
1021 (name . "^\\*scratch\\*$")
1022 (name . "^\\*Messages\\*$")))
1023 ("slack"
1024 (or
1025 (name . "^\\*Slack*"))))))
1026 (ibuffer-formats
1027 '((mark modified read-only locked " "
1028 (name 18 18 :left :elide)
1029 " "
1030 (size-h 9 -1 :right)
1031 " "
1032 (mode 16 16 :left :elide)
1033 " " filename-and-process)
1034 (mark " "
1035 (name 16 -1)
1036 " " filename)))
1037 :hook (ibuffer . (lambda () (ibuffer-switch-to-saved-filter-groups "default"))))
1038 #+end_src
1039
1040 *** Outline
1041
1042 #+begin_src emacs-lisp
1043 (use-package outline
1044 :defer t
1045 :hook (prog-mode . outline-minor-mode)
1046 :bind
1047 (:map
1048 outline-minor-mode-map
1049 ("<s-tab>" . outline-toggle-children)
1050 ("M-p" . outline-previous-visible-heading)
1051 ("M-n" . outline-next-visible-heading)
1052 :prefix-map amin--outline-prefix-map
1053 :prefix "s-o"
1054 ("TAB" . outline-toggle-children)
1055 ("a" . outline-hide-body)
1056 ("H" . outline-hide-body)
1057 ("S" . outline-show-all)
1058 ("h" . outline-hide-subtree)
1059 ("s" . outline-show-subtree)))
1060 #+end_src
1061
1062 * Borg's =layer/essentials=
1063
1064 TODO: break this giant source block down into individual org sections.
1065
1066 #+begin_src emacs-lisp
1067 (use-package dash
1068 :config (dash-enable-font-lock))
1069
1070 (use-package diff-hl
1071 :config
1072 (setq diff-hl-draw-borders nil)
1073 (global-diff-hl-mode)
1074 (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh t))
1075
1076 (use-package dired
1077 :defer t
1078 :config (setq dired-listing-switches "-alh"))
1079
1080 (use-package eldoc
1081 :when (version< "25" emacs-version)
1082 :config (global-eldoc-mode))
1083
1084 (use-package help
1085 :defer t
1086 :config
1087 (temp-buffer-resize-mode)
1088 (setq help-window-select t))
1089
1090 (progn ; `isearch'
1091 (setq isearch-allow-scroll t))
1092
1093 (use-package lisp-mode
1094 :config
1095 (add-hook 'emacs-lisp-mode-hook 'outline-minor-mode)
1096 (add-hook 'emacs-lisp-mode-hook 'reveal-mode)
1097 (defun indent-spaces-mode ()
1098 (setq indent-tabs-mode nil))
1099 (add-hook 'lisp-interaction-mode-hook #'indent-spaces-mode))
1100
1101 (use-package man
1102 :defer t
1103 :config (setq Man-width 80))
1104
1105 (use-package paren
1106 :config (show-paren-mode))
1107
1108 (use-package prog-mode
1109 :config (global-prettify-symbols-mode)
1110 (defun indicate-buffer-boundaries-left ()
1111 (setq indicate-buffer-boundaries 'left))
1112 (add-hook 'prog-mode-hook #'indicate-buffer-boundaries-left))
1113
1114 (use-package recentf
1115 :defer 0.5
1116 :config
1117 (add-to-list 'recentf-exclude "^/\\(?:ssh\\|su\\|sudo\\)?:")
1118 (setq recentf-max-saved-items 40))
1119
1120 (use-package savehist
1121 :config (savehist-mode))
1122
1123 (use-package saveplace
1124 :when (version< "25" emacs-version)
1125 :config (save-place-mode))
1126
1127 (use-package simple
1128 :config (column-number-mode))
1129
1130 (progn ; `text-mode'
1131 (add-hook 'text-mode-hook #'indicate-buffer-boundaries-left)
1132 (add-hook 'text-mode-hook #'abbrev-mode))
1133
1134 (use-package tramp
1135 :defer t
1136 :config
1137 (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:"))
1138 (add-to-list 'tramp-default-proxies-alist '("localhost" nil nil))
1139 (add-to-list 'tramp-default-proxies-alist
1140 (list (regexp-quote (system-name)) nil nil)))
1141
1142 #+end_src
1143
1144 * Editing
1145
1146 ** Company
1147
1148 #+begin_src emacs-lisp
1149 (use-package company
1150 :defer 1
1151 :bind
1152 (:map company-active-map
1153 ([tab] . company-complete-common-or-cycle)
1154 ([escape] . company-abort))
1155 :custom
1156 (company-minimum-prefix-length 1)
1157 (company-selection-wrap-around t)
1158 (company-dabbrev-char-regexp "\\sw\\|\\s_\\|[-_]")
1159 (company-dabbrev-downcase nil)
1160 (company-dabbrev-ignore-case nil)
1161 :config
1162 (global-company-mode t))
1163 #+end_src
1164
1165 * Syntax and spell checking
1166
1167 #+begin_src emacs-lisp
1168 (use-package flycheck
1169 :defer 3
1170 :hook (prog-mode . flycheck-mode)
1171 :bind
1172 (:map flycheck-mode-map
1173 ("M-P" . flycheck-previous-error)
1174 ("M-N" . flycheck-next-error))
1175 :config
1176 ;; Use the load-path from running Emacs when checking elisp files
1177 (setq flycheck-emacs-lisp-load-path 'inherit)
1178
1179 ;; Only flycheck when I actually save the buffer
1180 (setq flycheck-check-syntax-automatically '(mode-enabled save)))
1181
1182 ;; http://endlessparentheses.com/ispell-and-apostrophes.html
1183 (use-package ispell
1184 :defer 3
1185 :config
1186 ;; ’ can be part of a word
1187 (setq ispell-local-dictionary-alist
1188 `((nil "[[:alpha:]]" "[^[:alpha:]]"
1189 "['\x2019]" nil ("-B") nil utf-8)))
1190 ;; don't send ’ to the subprocess
1191 (defun endless/replace-apostrophe (args)
1192 (cons (replace-regexp-in-string
1193 "’" "'" (car args))
1194 (cdr args)))
1195 (advice-add #'ispell-send-string :filter-args
1196 #'endless/replace-apostrophe)
1197
1198 ;; convert ' back to ’ from the subprocess
1199 (defun endless/replace-quote (args)
1200 (if (not (derived-mode-p 'org-mode))
1201 args
1202 (cons (replace-regexp-in-string
1203 "'" "’" (car args))
1204 (cdr args))))
1205 (advice-add #'ispell-parse-output :filter-args
1206 #'endless/replace-quote))
1207 #+end_src
1208 * Programming modes
1209
1210 ** [[http://alloytools.org][Alloy]] (with [[https://github.com/dwwmmn/alloy-mode][alloy-mode]])
1211
1212 #+begin_src emacs-lisp
1213 (use-package alloy-mode
1214 :defer t
1215 :config (setq alloy-basic-offset 2))
1216 #+end_src
1217
1218 ** [[https://coq.inria.fr][Coq]] (with [[https://github.com/ProofGeneral/PG][Proof General]])
1219
1220 #+begin_src emacs-lisp
1221 (use-package proof-site ; Proof General
1222 :defer t
1223 :load-path "lib/proof-site/generic/")
1224 #+end_src
1225
1226 ** [[https://leanprover.github.io][Lean]] (with [[https://github.com/leanprover/lean-mode][lean-mode]])
1227
1228 #+begin_src emacs-lisp
1229 (eval-when-compile (defvar lean-mode-map))
1230 (use-package lean-mode
1231 :defer 1
1232 :bind (:map lean-mode-map
1233 ("S-SPC" . company-complete))
1234 :config
1235 (require 'lean-input)
1236 (setq default-input-method "Lean"
1237 lean-input-tweak-all '(lean-input-compose
1238 (lean-input-prepend "/")
1239 (lean-input-nonempty))
1240 lean-input-user-translations '(("/" "/")))
1241 (lean-input-setup))
1242 #+end_src
1243
1244 ** Haskell
1245
1246 *** [[https://github.com/haskell/haskell-mode][haskell-mode]]
1247
1248 #+begin_src emacs-lisp
1249 (use-package haskell-mode
1250 :defer t
1251 :config
1252 (setq haskell-indentation-layout-offset 4
1253 haskell-indentation-left-offset 4
1254 flycheck-checker 'haskell-hlint
1255 flycheck-disabled-checkers '(haskell-stack-ghc haskell-ghc)))
1256 #+end_src
1257
1258 *** [[https://github.com/jyp/dante][dante]]
1259
1260 #+begin_src emacs-lisp
1261 (use-package dante
1262 :after haskell-mode
1263 :commands dante-mode
1264 :hook (haskell-mode . dante-mode))
1265 #+end_src
1266
1267 *** [[https://github.com/mpickering/hlint-refactor-mode][hlint-refactor]]
1268
1269 Emacs bindings for [[https://github.com/ndmitchell/hlint][hlint]]'s refactor option. This requires the refact
1270 executable from [[https://github.com/mpickering/apply-refact][apply-refact]].
1271
1272 #+begin_src emacs-lisp
1273 (use-package hlint-refactor
1274 :after haskell-mode
1275 :bind (:map hlint-refactor-mode-map
1276 ("C-c l b" . hlint-refactor-refactor-buffer)
1277 ("C-c l r" . hlint-refactor-refactor-at-point))
1278 :hook (haskell-mode . hlint-refactor-mode))
1279 #+end_src
1280
1281 *** [[https://github.com/flycheck/flycheck-haskell][flycheck-haskell]]
1282
1283 #+begin_src emacs-lisp
1284 (use-package flycheck-haskell
1285 :after haskell-mode)
1286 #+end_src
1287
1288 *** [[https://github.com/ndmitchell/hlint/blob/20e116a043f2073c57b17b24ae6364b5e433ba7e/data/hs-lint.el][hs-lint.el]]
1289 :PROPERTIES:
1290 :header-args+: :tangle lisp/hs-lint.el :mkdirp yes
1291 :END:
1292
1293 Currently using =flycheck-haskell= with the =haskell-hlint= checker
1294 instead.
1295
1296 #+begin_src emacs-lisp :tangle no
1297 ;;; hs-lint.el --- minor mode for HLint code checking
1298
1299 ;; Copyright 2009 (C) Alex Ott
1300 ;;
1301 ;; Author: Alex Ott <alexott@gmail.com>
1302 ;; Keywords: haskell, lint, HLint
1303 ;; Requirements:
1304 ;; Status: distributed under terms of GPL2 or above
1305
1306 ;; Typical message from HLint looks like:
1307 ;;
1308 ;; /Users/ott/projects/lang-exp/haskell/test.hs:52:1: Eta reduce
1309 ;; Found:
1310 ;; count1 p l = length (filter p l)
1311 ;; Why not:
1312 ;; count1 p = length . filter p
1313
1314
1315 (require 'compile)
1316
1317 (defgroup hs-lint nil
1318 "Run HLint as inferior of Emacs, parse error messages."
1319 :group 'tools
1320 :group 'haskell)
1321
1322 (defcustom hs-lint-command "hlint"
1323 "The default hs-lint command for \\[hlint]."
1324 :type 'string
1325 :group 'hs-lint)
1326
1327 (defcustom hs-lint-save-files t
1328 "Save modified files when run HLint or no (ask user)"
1329 :type 'boolean
1330 :group 'hs-lint)
1331
1332 (defcustom hs-lint-replace-with-suggestions nil
1333 "Replace user's code with suggested replacements"
1334 :type 'boolean
1335 :group 'hs-lint)
1336
1337 (defcustom hs-lint-replace-without-ask nil
1338 "Replace user's code with suggested replacements automatically"
1339 :type 'boolean
1340 :group 'hs-lint)
1341
1342 (defun hs-lint-process-setup ()
1343 "Setup compilation variables and buffer for `hlint'."
1344 (run-hooks 'hs-lint-setup-hook))
1345
1346 ;; regex for replace suggestions
1347 ;;
1348 ;; ^\(.*?\):\([0-9]+\):\([0-9]+\): .*
1349 ;; Found:
1350 ;; \s +\(.*\)
1351 ;; Why not:
1352 ;; \s +\(.*\)
1353
1354 (defvar hs-lint-regex
1355 "^\\(.*?\\):\\([0-9]+\\):\\([0-9]+\\): .*[\n\C-m]Found:[\n\C-m]\\s +\\(.*\\)[\n\C-m]Why not:[\n\C-m]\\s +\\(.*\\)[\n\C-m]"
1356 "Regex for HLint messages")
1357
1358 (defun make-short-string (str maxlen)
1359 (if (< (length str) maxlen)
1360 str
1361 (concat (substring str 0 (- maxlen 3)) "...")))
1362
1363 (defun hs-lint-replace-suggestions ()
1364 "Perform actual replacement of suggestions"
1365 (goto-char (point-min))
1366 (while (re-search-forward hs-lint-regex nil t)
1367 (let* ((fname (match-string 1))
1368 (fline (string-to-number (match-string 2)))
1369 (old-code (match-string 4))
1370 (new-code (match-string 5))
1371 (msg (concat "Replace '" (make-short-string old-code 30)
1372 "' with '" (make-short-string new-code 30) "'"))
1373 (bline 0)
1374 (eline 0)
1375 (spos 0)
1376 (new-old-code ""))
1377 (save-excursion
1378 (switch-to-buffer (get-file-buffer fname))
1379 (goto-char (point-min))
1380 (forward-line (1- fline))
1381 (beginning-of-line)
1382 (setf bline (point))
1383 (when (or hs-lint-replace-without-ask
1384 (yes-or-no-p msg))
1385 (end-of-line)
1386 (setf eline (point))
1387 (beginning-of-line)
1388 (setf old-code (regexp-quote old-code))
1389 (while (string-match "\\\\ " old-code spos)
1390 (setf new-old-code (concat new-old-code
1391 (substring old-code spos (match-beginning 0))
1392 "\\ *"))
1393 (setf spos (match-end 0)))
1394 (setf new-old-code (concat new-old-code (substring old-code spos)))
1395 (remove-text-properties bline eline '(composition nil))
1396 (when (re-search-forward new-old-code eline t)
1397 (replace-match new-code nil t)))))))
1398
1399 (defun hs-lint-finish-hook (buf msg)
1400 "Function, that is executed at the end of HLint execution"
1401 (if hs-lint-replace-with-suggestions
1402 (hs-lint-replace-suggestions)
1403 (next-error 1 t)))
1404
1405 (define-compilation-mode hs-lint-mode "HLint"
1406 "Mode for check Haskell source code."
1407 (set (make-local-variable 'compilation-process-setup-function)
1408 'hs-lint-process-setup)
1409 (set (make-local-variable 'compilation-disable-input) t)
1410 (set (make-local-variable 'compilation-scroll-output) nil)
1411 (set (make-local-variable 'compilation-finish-functions)
1412 (list 'hs-lint-finish-hook))
1413 )
1414
1415 (defun hs-lint ()
1416 "Run HLint for current buffer with haskell source"
1417 (interactive)
1418 (save-some-buffers hs-lint-save-files)
1419 (compilation-start (concat hs-lint-command " \"" buffer-file-name "\"")
1420 'hs-lint-mode))
1421
1422 (provide 'hs-lint)
1423 ;;; hs-lint.el ends here
1424 #+end_src
1425
1426 #+begin_src emacs-lisp :tangle no
1427 (use-package hs-lint
1428 :load-path "lisp/"
1429 :bind (:map haskell-mode-map
1430 ("C-c l l" . hs-lint)))
1431 #+end_src
1432
1433 ** Web dev
1434
1435 *** SGML and HTML
1436
1437 #+begin_src emacs-lisp
1438 (use-package sgml-mode
1439 :defer t
1440 :config
1441 (setq sgml-basic-offset 2))
1442 #+end_src
1443
1444 *** CSS and SCSS
1445
1446 #+begin_src emacs-lisp
1447 (use-package css-mode
1448 :defer t
1449 :config
1450 (setq css-indent-offset 2))
1451 #+end_src
1452
1453 *** Web mode
1454
1455 #+begin_src emacs-lisp
1456 (use-package web-mode
1457 :defer t
1458 :mode "\\.html\\'"
1459 :config
1460 (setq-every! 2
1461 web-mode-code-indent-offset
1462 web-mode-css-indent-offset
1463 web-mode-markup-indent-offset))
1464 #+end_src
1465
1466 *** Emmet mode
1467
1468 #+begin_src emacs-lisp
1469 (use-package emmet-mode
1470 :after (:any web-mode css-mode sgml-mode)
1471 :bind* (("C-)" . emmet-next-edit-point)
1472 ("C-(" . emmet-prev-edit-point))
1473 :config
1474 (unbind-key "C-j" emmet-mode-keymap)
1475 (setq emmet-move-cursor-between-quotes t)
1476 :hook (web-mode css-mode html-mode sgml-mode))
1477 #+end_src
1478
1479 ** Nix
1480
1481 #+begin_src emacs-lisp
1482 (use-package nix-mode
1483 :defer t
1484 :mode "\\.nix\\'")
1485 #+end_src
1486
1487 ** Java
1488
1489 *** meghanada
1490
1491 #+begin_src emacs-lisp :tangle no
1492 (use-package meghanada
1493 :bind
1494 (:map meghanada-mode-map
1495 (("C-M-o" . meghanada-optimize-import)
1496 ("C-M-t" . meghanada-import-all)))
1497 :hook (java-mode . meghanada-mode))
1498 #+end_src
1499
1500 *** lsp-java
1501
1502 #+begin_comment
1503 dependencies:
1504
1505 ace-window
1506 avy
1507 bui
1508 company-lsp
1509 dap-mode
1510 lsp-java
1511 lsp-mode
1512 lsp-ui
1513 pfuture
1514 tree-mode
1515 treemacs
1516 #+end_comment
1517
1518 #+begin_src emacs-lisp :tangle no
1519 (use-package treemacs
1520 :config (setq treemacs-never-persist t))
1521
1522 (use-package yasnippet
1523 :config
1524 ;; (yas-global-mode)
1525 )
1526
1527 (use-package lsp-mode
1528 :init (setq lsp-eldoc-render-all nil
1529 lsp-highlight-symbol-at-point nil)
1530 )
1531
1532 (use-package hydra)
1533
1534 (use-package company-lsp
1535 :after company
1536 :config
1537 (setq company-lsp-cache-candidates t
1538 company-lsp-async t))
1539
1540 (use-package lsp-ui
1541 :config
1542 (setq lsp-ui-sideline-update-mode 'point))
1543
1544 (use-package lsp-java
1545 :config
1546 (add-hook 'java-mode-hook
1547 (lambda ()
1548 (setq-local company-backends (list 'company-lsp))))
1549
1550 (add-hook 'java-mode-hook 'lsp-java-enable)
1551 (add-hook 'java-mode-hook 'flycheck-mode)
1552 (add-hook 'java-mode-hook 'company-mode)
1553 (add-hook 'java-mode-hook 'lsp-ui-mode))
1554
1555 (use-package dap-mode
1556 :after lsp-mode
1557 :config
1558 (dap-mode t)
1559 (dap-ui-mode t))
1560
1561 (use-package dap-java
1562 :after (lsp-java))
1563
1564 (use-package lsp-java-treemacs
1565 :after (treemacs))
1566 #+end_src
1567
1568 * Emacs Enhancements
1569
1570 ** [[https://github.com/justbur/emacs-which-key][which-key]]
1571
1572 #+begin_quote
1573 Emacs package that displays available keybindings in popup
1574 #+end_quote
1575
1576 #+begin_src emacs-lisp
1577 (use-package which-key
1578 :defer 1
1579 :config (which-key-mode))
1580 #+end_src
1581
1582 ** theme
1583
1584 #+begin_src emacs-lisp
1585 (add-to-list 'custom-theme-load-path "~/.emacs.d/lisp")
1586 (load-theme 'tangomod t)
1587 #+end_src
1588
1589 ** doom-modeline
1590
1591 #+begin_src emacs-lisp
1592 (use-package doom-modeline
1593 :demand t
1594 :config (setq doom-modeline-height 32)
1595 :hook (after-init . doom-modeline-init))
1596 #+end_src
1597
1598 ** doom-themes
1599
1600 #+begin_src emacs-lisp
1601 (use-package doom-themes)
1602 #+end_src
1603
1604 ** theme helper functions
1605
1606 #+begin_src emacs-lisp
1607 (defun amin/lights-on ()
1608 "Enable my favourite light theme."
1609 (interactive)
1610 (progn
1611 (mapc #'disable-theme custom-enabled-themes)
1612 (load-theme 'tangomod t)))
1613
1614 (defun amin/lights-off ()
1615 "Go dark."
1616 (interactive)
1617 (progn
1618 (mapc #'disable-theme custom-enabled-themes)
1619 (load-theme 'doom-tomorrow-night t)))
1620
1621 (bind-keys
1622 ("s-t d" . amin/lights-off)
1623 ("s-t l" . amin/lights-on))
1624 #+end_src
1625
1626 ** [[https://github.com/bbatsov/crux][crux]]
1627
1628 #+begin_src emacs-lisp
1629 (use-package crux
1630 :defer 1
1631 :bind (("C-c b k" . crux-kill-other-buffers)
1632 ("C-c d" . crux-duplicate-current-line-or-region)
1633 ("C-c D" . crux-duplicate-and-comment-current-line-or-region)
1634 ("C-c f c" . crux-copy-file-preserve-attributes)
1635 ("C-c f d" . crux-delete-file-and-buffer)
1636 ("C-c f r" . crux-rename-file-and-buffer)
1637 ("C-c j" . crux-top-join-line)
1638 ("C-S-j" . crux-top-join-line)))
1639 #+end_src
1640
1641 ** [[https://github.com/alezost/mwim.el][mwim]]
1642
1643 #+begin_src emacs-lisp
1644 (use-package mwim
1645 :bind (("C-a" . mwim-beginning-of-code-or-line)
1646 ("C-e" . mwim-end-of-code-or-line)
1647 ("<home>" . mwim-beginning-of-line-or-code)
1648 ("<end>" . mwim-end-of-line-or-code)))
1649 #+end_src
1650
1651 ** projectile
1652
1653 #+begin_src emacs-lisp
1654 (use-package projectile
1655 :defer t
1656 :bind-keymap ("C-c p" . projectile-command-map)
1657 :config
1658 (projectile-mode)
1659
1660 (defun my-projectile-invalidate-cache (&rest _args)
1661 ;; ignore the args to `magit-checkout'
1662 (projectile-invalidate-cache nil))
1663
1664 (eval-after-load 'magit-branch
1665 '(progn
1666 (advice-add 'magit-checkout
1667 :after #'my-projectile-invalidate-cache)
1668 (advice-add 'magit-branch-and-checkout
1669 :after #'my-projectile-invalidate-cache))))
1670 #+end_src
1671
1672 ** [[https://github.com/Wilfred/helpful][helpful]]
1673
1674 #+begin_src emacs-lisp
1675 (use-package helpful
1676 :defer 1
1677 :bind
1678 (("C-S-h c" . helpful-command)
1679 ("C-S-h f" . helpful-callable) ; helpful-function
1680 ("C-S-h v" . helpful-variable)
1681 ("C-S-h k" . helpful-key)
1682 ("C-S-h p" . helpful-at-point)))
1683 #+end_src
1684
1685 ** [[https://github.com/knu/shell-toggle.el][shell-toggle]]
1686
1687 #+begin_src emacs-lisp
1688 (use-package shell-toggle
1689 :after eshell
1690 :bind ("C-c a s e" . amin/shell-toggle)
1691 :config
1692 (defun amin/shell-toggle (make-cd)
1693 "Toggle between the shell buffer and whatever buffer you are editing.
1694 With a prefix argument MAKE-CD also insert a \"cd DIR\" command
1695 into the shell, where DIR is the directory of the current buffer.
1696
1697 When called in the shell buffer returns you to the buffer you were editing
1698 before calling this the first time.
1699
1700 Options: `shell-toggle-goto-eob'"
1701 (interactive "P")
1702 ;; Try to decide on one of three possibilities:
1703 ;; If not in shell-buffer, switch to it.
1704 ;; If in shell-buffer, return to state before going to the shell-buffer
1705 (if (eq (current-buffer) shell-toggle-shell-buffer)
1706 (shell-toggle-buffer-return-from-shell)
1707 (progn
1708 (shell-toggle-buffer-goto-shell make-cd)
1709 (if shell-toggle-full-screen-window-only (delete-other-windows)))))
1710
1711 ;; override to split horizontally instead
1712 (defun shell-toggle-buffer-switch-to-other-window ()
1713 "Switch to other window.
1714 If the current window is the only window in the current frame,
1715 create a new window and switch to it.
1716
1717 \(This is less intrusive to the current window configuration than
1718 `switch-buffer-other-window')"
1719 (let ((this-window (selected-window)))
1720 (other-window 1)
1721 ;; If we did not switch window then we only have one window and need to
1722 ;; create a new one.
1723 (if (eq this-window (selected-window))
1724 (progn
1725 (split-window-horizontally)
1726 (other-window 1)))))
1727
1728 :custom
1729 (shell-toggle-launch-shell 'shell-toggle-eshell))
1730 #+end_src
1731
1732 ** [[https://github.com/EricCrosson/unkillable-scratch][unkillable-scratch]]
1733
1734 Make =*scratch*= and =*Messages*= unkillable.
1735
1736 #+begin_src emacs-lisp
1737 (use-package unkillable-scratch
1738 :defer 3
1739 :config
1740 (unkillable-scratch 1)
1741 :custom
1742 (unkillable-scratch-behavior 'do-nothing)
1743 (unkillable-buffers '("^\\*scratch\\*$" "^\\*Messages\\*$")))
1744 #+end_src
1745
1746 ** [[https://github.com/davep/boxquote.el][boxquote.el]]
1747
1748 #+begin_example
1749 ,----
1750 | make pretty boxed quotes like this
1751 `----
1752 #+end_example
1753
1754 #+begin_src emacs-lisp
1755 (use-package boxquote
1756 :defer 3
1757 :bind
1758 (:prefix-map amin--boxquote-prefix-map
1759 :prefix "C-c q"
1760 ("b" . boxquote-buffer)
1761 ("B" . boxquote-insert-buffer)
1762 ("d" . boxquote-defun)
1763 ("F" . boxquote-insert-file)
1764 ("hf" . boxquote-describe-function)
1765 ("hk" . boxquote-describe-key)
1766 ("hv" . boxquote-describe-variable)
1767 ("hw" . boxquote-where-is)
1768 ("k" . boxquote-kill)
1769 ("p" . boxquote-paragraph)
1770 ("q" . boxquote-boxquote)
1771 ("r" . boxquote-region)
1772 ("s" . boxquote-shell-command)
1773 ("t" . boxquote-text)
1774 ("T" . boxquote-title)
1775 ("u" . boxquote-unbox)
1776 ("U" . boxquote-unbox-region)
1777 ("y" . boxquote-yank)
1778 ("M-q" . boxquote-fill-paragraph)
1779 ("M-w" . boxquote-kill-ring-save)))
1780 #+end_src
1781
1782 Also see [[https://www.emacswiki.org/emacs/rebox2][rebox2]].
1783
1784 ** [[https://github.com/DarthFennec/highlight-indent-guides][highlight-indent-guides]]
1785
1786 #+begin_src emacs-lisp
1787 (use-package highlight-indent-guides
1788 :disabled t
1789 :defer 3
1790 :hook ((prog-mode . highlight-indent-guides-mode)
1791 ;; (org-mode . highlight-indent-guides-mode)
1792 )
1793 :config
1794 (setq highlight-indent-guides-character ?\|)
1795 (setq highlight-indent-guides-auto-enabled nil)
1796 (setq highlight-indent-guides-method 'character)
1797 (setq highlight-indent-guides-responsive 'top)
1798 (set-face-foreground 'highlight-indent-guides-character-face "gainsboro")
1799 (set-face-foreground 'highlight-indent-guides-top-character-face "grey40")) ; grey13 is nice too
1800 #+end_src
1801
1802 ** pdf-tools
1803
1804 #+begin_src emacs-lisp
1805 (use-package pdf-tools
1806 :defer t
1807 :magic ("%PDF" . pdf-view-mode)
1808 :config
1809 (setq pdf-view-resize-factor 1.05)
1810 (pdf-tools-install)
1811 :bind
1812 (:map pdf-view-mode-map
1813 ("C-s" . isearch-forward)
1814 ("C-r" . isearch-backward)
1815 ("j" . pdf-view-next-line-or-next-page)
1816 ("k" . pdf-view-previous-line-or-previous-page)
1817 ("h" . image-backward-hscroll)
1818 ("l" . image-forward-hscroll)))
1819 #+end_src
1820
1821 ** anzu
1822
1823 #+begin_src emacs-lisp
1824 (use-package anzu)
1825 #+end_src
1826
1827 ** typo.el
1828
1829 #+begin_src emacs-lisp
1830 (use-package typo
1831 :defer 2
1832 :config
1833 (typo-global-mode 1)
1834 :hook (text-mode . typo-mode))
1835 #+end_src
1836
1837 ** hl-todo
1838
1839 #+begin_src emacs-lisp
1840 (use-package hl-todo
1841 :defer 4
1842 :config
1843 (global-hl-todo-mode))
1844 #+end_src
1845
1846 ** shrink-path
1847
1848 #+begin_src emacs-lisp
1849 (use-package shrink-path
1850 :after eshell
1851 :config
1852 (setq eshell-prompt-regexp "\\(.*\n\\)*λ "
1853 eshell-prompt-function #'+eshell/prompt)
1854
1855 (defun +eshell/prompt ()
1856 (let ((base/dir (shrink-path-prompt default-directory)))
1857 (concat (propertize (car base/dir)
1858 'face 'font-lock-comment-face)
1859 (propertize (cdr base/dir)
1860 'face 'font-lock-constant-face)
1861 (propertize (+eshell--current-git-branch)
1862 'face 'font-lock-function-name-face)
1863 "\n"
1864 (propertize "λ" 'face 'eshell-prompt-face)
1865 ;; needed for the input text to not have prompt face
1866 (propertize " " 'face 'default))))
1867
1868 (defun +eshell--current-git-branch ()
1869 (let ((branch (car (loop for match in (split-string (shell-command-to-string "git branch") "\n")
1870 when (string-match "^\*" match)
1871 collect match))))
1872 (if (not (eq branch nil))
1873 (concat " " (substring branch 2))
1874 ""))))
1875 #+end_src
1876
1877 ** slack
1878
1879 Hopefully temporary.
1880
1881 #+begin_src emacs-lisp
1882 (use-package slack
1883 :disabled t
1884 :commands (slack-start)
1885 :init
1886 (eval-when-compile ; silence the byte-compiler
1887 (defvar url-http-data nil)
1888 (defvar url-http-extra-headers nil)
1889 (defvar url-http-method nil)
1890 (defvar url-callback-function nil)
1891 (defvar url-callback-arguments nil)
1892 (defvar oauth--token-data nil))
1893 (setq slack-buffer-emojify t
1894 slack-prefer-current-team t)
1895 :config
1896 (slack-register-team
1897 :name "uw-apv"
1898 :default t
1899 :client-id uw-apv-client-id
1900 :client-secret uw-apv-client-secret
1901 :token uw-apv-token
1902 :subscribed-channels '(general)
1903 :full-and-display-names t)
1904 (slack-register-team
1905 :name "watform"
1906 :default nil
1907 :client-id watform-client-id
1908 :client-secret watform-client-secret
1909 :token watform-token
1910 :subscribed-channels '(general)
1911 :full-and-display-names t)
1912 (add-to-list 'swiper-font-lock-exclude 'slack-message-buffer-mode t)
1913 (setq lui-time-stamp-format "[%Y-%m-%d %H:%M:%S]"
1914 lui-time-stamp-only-when-changed-p t
1915 lui-time-stamp-position 'right)
1916 :bind
1917 (("C-c s s" . slack-start)
1918 ("C-c s u" . slack-select-unread-rooms)
1919 ("C-c s b" . slack-select-rooms)
1920 ("C-c s t" . slack-change-current-team)
1921 ("C-c s c" . slack-ws-close)
1922 :map slack-mode-map
1923 ("M-p" . slack-buffer-goto-prev-message)
1924 ("M-n" . slack-buffer-goto-next-message)
1925 ("C-c e" . slack-message-edit)
1926 ("C-c k" . slack-message-delete)
1927 ("C-c C-k" . slack-channel-leave)
1928 ("C-c r a" . slack-message-add-reaction)
1929 ("C-c r r" . slack-message-remove-reaction)
1930 ("C-c r s" . slack-message-show-reaction-users)
1931 ("C-c p l" . slack-room-pins-list)
1932 ("C-c p a" . slack-message-pins-add)
1933 ("C-c p r" . slack-message-pins-remove)
1934 ("@" . slack-message-embed-mention)
1935 ("#" . slack-message-embed-channel)))
1936
1937 (use-package alert
1938 :commands (alert)
1939 :init
1940 (setq alert-default-style 'notifier))
1941 #+end_src
1942
1943 ** [[https://github.com/peterwvj/eshell-up][eshell-up]]
1944
1945 #+begin_src emacs-lisp
1946 (use-package eshell-up
1947 :after eshell)
1948 #+end_src
1949
1950 ** multi-term
1951
1952 #+begin_src emacs-lisp
1953 (use-package multi-term
1954 :defer 1
1955 :bind (("C-c C-j" . term-line-mode)
1956 ("C-c a s m m" . multi-term)
1957 ("C-c a s m p" . multi-term-dedicated-toggle))
1958 :config
1959 (setq multi-term-program "/bin/screen"
1960 ;; TODO: add separate bindings for connecting to existing
1961 ;; session vs. always creating a new one
1962 multi-term-dedicated-select-after-open-p t
1963 multi-term-dedicated-window-height 20
1964 multi-term-dedicated-max-window-height 30
1965 term-bind-key-alist
1966 '(("C-c C-c" . term-interrupt-subjob)
1967 ("C-c C-e" . term-send-esc)
1968 ("C-k" . kill-line)
1969 ("C-y" . term-paste)
1970 ("M-f" . term-send-forward-word)
1971 ("M-b" . term-send-backward-word)
1972 ("M-p" . term-send-up)
1973 ("M-n" . term-send-down)
1974 ("<C-backspace>" . term-send-backward-kill-word)
1975 ("<M-DEL>" . term-send-backward-kill-word)
1976 ("M-d" . term-send-delete-word)
1977 ("M-," . term-send-raw)
1978 ("M-." . comint-dynamic-complete))
1979 term-unbind-key-alist
1980 '("C-z" "C-x" "C-c" "C-h" "C-y" "<ESC>")))
1981 #+end_src
1982
1983 ** page-break-lines
1984
1985 #+begin_src emacs-lisp
1986 (use-package page-break-lines
1987 :config
1988 (global-page-break-lines-mode))
1989 #+end_src
1990
1991 * Email
1992
1993 #+begin_src emacs-lisp
1994 (defvar amin-maildir (expand-file-name "~/mail/"))
1995 (after! recentf
1996 (add-to-list 'recentf-exclude amin-maildir))
1997 #+end_src
1998
1999 ** Gnus
2000
2001 #+begin_src emacs-lisp
2002 (setq
2003 amin-gnus-init-file (no-littering-expand-etc-file-name "gnus")
2004 mail-user-agent 'gnus-user-agent
2005 read-mail-command 'gnus)
2006
2007 (use-package gnus
2008 :bind (("s-m" . gnus)
2009 ("s-M" . gnus-unplugged))
2010 :init
2011 (setq
2012 gnus-select-method '(nnnil "")
2013 gnus-secondary-select-methods
2014 '((nnimap "amin"
2015 (nnimap-stream plain)
2016 (nnimap-address "127.0.0.1")
2017 (nnimap-server-port 143)
2018 (nnimap-authenticator plain)
2019 (nnimap-user "amin@aminb.org"))
2020 (nnimap "uwaterloo"
2021 (nnimap-stream plain)
2022 (nnimap-address "127.0.0.1")
2023 (nnimap-server-port 143)
2024 (nnimap-authenticator plain)
2025 (nnimap-user "abandali@uwaterloo.ca")))
2026 gnus-message-archive-group "nnimap+amin:Sent"
2027 gnus-parameters
2028 '(("gnu.*"
2029 (gcc-self . t)))
2030 gnus-large-newsgroup 50
2031 gnus-home-directory (no-littering-expand-var-file-name "gnus/")
2032 gnus-directory (concat gnus-home-directory "news/")
2033 message-directory (concat gnus-home-directory "mail/")
2034 nndraft-directory (concat gnus-home-directory "drafts/")
2035 gnus-save-newsrc-file nil
2036 gnus-read-newsrc-file nil
2037 gnus-interactive-exit nil
2038 gnus-gcc-mark-as-read t))
2039
2040 (use-package gnus-art
2041 :config
2042 (setq
2043 gnus-visible-headers
2044 (concat gnus-visible-headers "\\|^List-Id:\\|^X-RT-Originator:\\|^User-Agent:")
2045 gnus-sorted-header-list
2046 '("^From:" "^Subject:" "^Summary:" "^Keywords:"
2047 "^Followup-To:" "^To:" "^Cc:" "X-RT-Originator"
2048 "^Newsgroups:" "List-Id:" "^Organization:"
2049 "^User-Agent:" "^Date:")
2050 ;; local-lapsed article dates
2051 ;; from https://www.emacswiki.org/emacs/GnusFormatting#toc11
2052 gnus-article-date-headers '(user-defined)
2053 gnus-article-time-format
2054 (lambda (time)
2055 (let* ((date (format-time-string "%a, %d %b %Y %T %z" time))
2056 (local (article-make-date-line date 'local))
2057 (combined-lapsed (article-make-date-line date
2058 'combined-lapsed))
2059 (lapsed (progn
2060 (string-match " (.+" combined-lapsed)
2061 (match-string 0 combined-lapsed))))
2062 (concat local lapsed))))
2063 (bind-keys
2064 :map gnus-article-mode-map
2065 ("r" . gnus-article-reply-with-original)
2066 ("R" . gnus-article-wide-reply-with-original)
2067 ("M-L" . org-store-link)))
2068
2069 (use-package gnus-sum
2070 :bind (:map gnus-summary-mode-map
2071 :prefix-map amin--gnus-summary-prefix-map
2072 :prefix "v"
2073 ("r" . gnus-summary-reply)
2074 ("w" . gnus-summary-wide-reply)
2075 ("v" . gnus-summary-show-raw-article))
2076 :config
2077 (bind-keys
2078 :map gnus-summary-mode-map
2079 ("r" . gnus-summary-reply-with-original)
2080 ("R" . gnus-summary-wide-reply-with-original)
2081 ("M-L" . org-store-link))
2082 :hook (gnus-summary-mode . amin--no-mouse-autoselect-window))
2083
2084 (use-package gnus-msg
2085 :config
2086 (setq gnus-posting-styles
2087 '((".*"
2088 (address "amin@aminb.org")
2089 (body "\nBest,\namin\n")
2090 (eval (setq amin--message-cite-say-hi t)))
2091 ("gnu.*"
2092 (address "bandali@gnu.org"))
2093 ((header "subject" "ThankCRM")
2094 (to "webmasters-comment@gnu.org")
2095 (body "\nAdded to 2018supporters.html.\n\nMoving to campaigns.\n\n-amin\n")
2096 (eval (setq amin--message-cite-say-hi nil)))
2097 ("nnimap\\+uwaterloo:.*"
2098 (address "abandali@uwaterloo.ca")
2099 (gcc "\"nnimap+uwaterloo:Sent Items\"")))))
2100
2101 (use-package gnus-topic
2102 :hook (gnus-group-mode . gnus-topic-mode))
2103
2104 (use-package gnus-agent
2105 :config
2106 (setq gnus-agent-synchronize-flags 'ask)
2107 :hook (gnus-group-mode . gnus-agent-mode))
2108
2109 (use-package gnus-group
2110 :config
2111 (setq gnus-permanently-visible-groups "\\((INBOX\\|gnu$\\)"))
2112
2113 (use-package mm-decode
2114 :config
2115 (setq mm-discouraged-alternatives '("text/html" "text/richtext")))
2116 #+end_src
2117
2118 ** sendmail
2119
2120 #+begin_src emacs-lisp
2121 (use-package sendmail
2122 :config
2123 (setq sendmail-program "/usr/bin/msmtp"
2124 ;; message-sendmail-extra-arguments '("-v" "-d")
2125 mail-specify-envelope-from t
2126 mail-envelope-from 'header))
2127 #+end_src
2128
2129 ** message
2130
2131 #+begin_src emacs-lisp
2132 (use-package message
2133 :config
2134 (defconst amin--message-cite-style-format "On %Y-%m-%d %l:%M %p, %N wrote:")
2135 (defconst message-cite-style-bandali
2136 '((message-cite-function 'message-cite-original)
2137 (message-citation-line-function 'message-insert-formatted-citation-line)
2138 (message-cite-reply-position 'traditional)
2139 (message-yank-prefix "> ")
2140 (message-yank-cited-prefix ">")
2141 (message-yank-empty-prefix ">")
2142 (message-citation-line-format
2143 (if amin--message-cite-say-hi
2144 (concat "Hi %F,\n\n" amin--message-cite-style-format)
2145 amin--message-cite-style-format)))
2146 "Citation style based on Mozilla Thunderbird's. Use with message-cite-style.")
2147 (setq message-cite-style 'message-cite-style-bandali
2148 message-kill-buffer-on-exit t
2149 message-send-mail-function 'message-send-mail-with-sendmail
2150 message-sendmail-envelope-from 'header
2151 message-dont-reply-to-names
2152 "\\(\\(.*@aminb\\.org\\)\\|\\(amin@bandali\\.me\\)\\|\\(\\(aminb?\\|mab\\|bandali\\)@gnu\\.org\\)\\|\\(\\(m\\|a\\(min\\.\\)?\\)bandali@uwaterloo\\.ca\\)\\)"
2153 message-user-fqdn "aminb.org")
2154 :hook (;; (message-setup . mml-secure-message-sign-pgpmime)
2155 (message-mode . flyspell-mode)
2156 (message-mode . (lambda ()
2157 ;; (setq fill-column 65
2158 ;; message-fill-column 65)
2159 (make-local-variable 'company-idle-delay)
2160 (setq company-idle-delay 0.2))))
2161 ;; :custom-face
2162 ;; (message-header-subject ((t (:foreground "#111" :weight semi-bold))))
2163 ;; (message-header-to ((t (:foreground "#111" :weight normal))))
2164 ;; (message-header-cc ((t (:foreground "#333" :weight normal))))
2165 )
2166
2167 (after! mml-sec
2168 (setq mml-secure-openpgp-encrypt-to-self t
2169 mml-secure-openpgp-sign-with-sender t))
2170 #+end_src
2171
2172 ** footnote
2173
2174 Convenient footnotes in =message-mode=.
2175
2176 #+begin_src emacs-lisp
2177 (use-package footnote
2178 :after message
2179 :bind
2180 (:map message-mode-map
2181 :prefix-map amin--footnote-prefix-map
2182 :prefix "C-c f"
2183 ("a" . footnote-add-footnote)
2184 ("b" . footnote-back-to-message)
2185 ("c" . footnote-cycle-style)
2186 ("d" . footnote-delete-footnote)
2187 ("g" . footnote-goto-footnote)
2188 ("r" . footnote-renumber-footnotes)
2189 ("s" . footnote-set-style))
2190 :config
2191 (setq footnote-start-tag ""
2192 footnote-end-tag ""
2193 footnote-style 'unicode))
2194 #+end_src
2195
2196 ** bbdb
2197
2198 Manually install bbdb (=lisp/bbdb= copied from an ELPA-based setup),
2199 because installing it from source on Emacs 27 using the following
2200 submodule configuration for some reason doesn’t work and results in
2201 very strange errors when using any of the functions.
2202
2203 #+begin_src conf :tangle no
2204 [submodule "bbdb"]
2205 path = lib/bbdb
2206 url = https://git.savannah.nongnu.org/git/bbdb.git
2207 load-path = lisp
2208 info-path = doc
2209 build-step = ./autogen.sh
2210 build-step = ./configure
2211 build-step = make
2212 build-step = make install
2213 #+end_src
2214
2215 I tried using =borg-elpa= instead of doing it like this, but it added
2216 2 seconds to my startup time, which is unacceptable to me.
2217
2218 #+begin_src emacs-lisp
2219 (use-package bbdb
2220 :load-path "lisp/bbdb"
2221 :init
2222 (load (expand-file-name "lisp/bbdb/bbdb-autoloads.el" user-emacs-directory))
2223 ;; (bbdb-mua-auto-update-init 'message)
2224 (setq bbdb-mua-auto-update-p 'query
2225 bbdb-complete-mail nil)
2226 (bbdb-initialize 'gnus 'message))
2227 #+end_src
2228
2229 ** message-x
2230
2231 #+begin_src emacs-lisp
2232 (use-package message-x
2233 :disabled t
2234 :custom
2235 (message-x-completion-alist
2236 (quote
2237 (("\\([rR]esent-\\|[rR]eply-\\)?[tT]o:\\|[bB]?[cC][cC]:" . gnus-harvest-find-address)
2238 ((if
2239 (boundp
2240 (quote message-newgroups-header-regexp))
2241 message-newgroups-header-regexp message-newsgroups-header-regexp)
2242 . message-expand-group)))))
2243 #+end_src
2244
2245 ** gnus-harvest
2246
2247 #+begin_src emacs-lisp
2248 (use-package gnus-harvest
2249 :disabled t
2250 :commands gnus-harvest-install
2251 :demand t
2252 :config
2253 (if (featurep 'message-x)
2254 (gnus-harvest-install 'message-x)
2255 (gnus-harvest-install)))
2256 #+end_src
2257
2258 * Blogging
2259
2260 ** [[https://ox-hugo.scripter.co][ox-hugo]]
2261
2262 #+begin_src emacs-lisp
2263 (use-package ox-hugo
2264 :after ox)
2265
2266 (use-package ox-hugo-auto-export
2267 :load-path "lib/ox-hugo")
2268 #+end_src
2269
2270 * Post initialization
2271 :PROPERTIES:
2272 :CUSTOM_ID: post-initialization
2273 :END:
2274
2275 Display how long it took to load the init file.
2276
2277 #+begin_src emacs-lisp
2278 (message "Loading %s...done (%.3fs)" user-init-file
2279 (float-time (time-subtract (current-time)
2280 amin--before-user-init-time)))
2281 #+end_src
2282
2283 * Footer
2284 :PROPERTIES:
2285 :CUSTOM_ID: footer
2286 :END:
2287
2288 #+begin_src emacs-lisp :comments none
2289 ;;; init.el ends here
2290 #+end_src
2291
2292 * COMMENT Local Variables :ARCHIVE:
2293 # Local Variables:
2294 # eval: (add-hook 'after-save-hook #'amin/async-babel-tangle 'append 'local)
2295 # End: