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