]> git.eshelyaron.com Git - dotfiles.git/commitdiff
Update Emacs config
authorEshel Yaron <me@eshelyaron.com>
Thu, 6 Apr 2023 13:54:34 +0000 (16:54 +0300)
committerEshel Yaron <me@eshelyaron.com>
Thu, 6 Apr 2023 13:54:34 +0000 (16:54 +0300)
.emacs.d/early-init.el
.emacs.d/init.el
.emacs.d/lisp/esy-capture.el [new file with mode: 0644]

index 5bde9bd5523f769333d0b971192c992b0cf0ea2c..5a555e714c61db682dbb5028b78b4b7efd58cf0c 100644 (file)
@@ -3,17 +3,8 @@
 
 ;;; Code:
 
-(defvar esy/init-step-times (list (cons nil (current-time))))
-
-(defun esy/init-step (step)
-  (push (cons step (current-time))
-        esy/init-step-times))
-
 (tool-bar-mode -1)
-(esy/init-step 'tool-bar-mode)
-
 (set-scroll-bar-mode nil)
-(esy/init-step 'scroll-bar-mode)
 
 (provide 'early-init)
 ;;; early-init.el ends here
index 167c9e8a86f8d25c4cab4567ed88ec806f970ba7..b45b1f0c220ce3664ffc2fe2fafbf6e2f6f4576a 100644 (file)
 
 ;;; Code:
 
-(esy/init-step 'init)
-
-;;;; temporarly increase GC threshold to expedite Emacs startup
-(let ((normal-gc-cons-threshold (* 20 1024 1024))
-      (init-gc-cons-threshold (* 1024 1024 1024))
-      (normal-mode-line-format mode-line-format))
-  (setq gc-cons-threshold init-gc-cons-threshold
-        mode-line-format nil)
-  (add-hook 'after-init-hook
-            (lambda () (setq gc-cons-threshold normal-gc-cons-threshold
-                             mode-line-format normal-mode-line-format))))
-(esy/init-step 'gc)
-
-(setq
- ;; my name
- user-full-name "Eshel Yaron"
- ;; my email address
- user-mail-address "me@eshelyaron.com"
- ;; direct Custom definitions to some file and forget about it
- custom-file (expand-file-name "custom.el" user-emacs-directory)
- ;; silence native compilation warnings
- native-comp-async-report-warnings-errors 'silent
- ;; only display the warning buffer if something's really broken
- warning-minimum-level :error
- ;; don't use stale .elc files
- load-prefer-newer t
- ;; maximize Emacs on startup
- initial-frame-alist '((fullscreen . fullboth))
- ;; disable popup dialogs
- use-dialog-box nil
- ;; disable splash screen
- inhibit-startup-screen t
- ;; make the scratch message more concise
- initial-scratch-message ";; Go.\n"
- ;; don't ring the bell
- ring-bell-function #'ignore
- ;; make C-x b obey display-buffer-alist
- switch-to-buffer-obey-display-actions t
- ;; disable new mail mode line indication
- display-time-mail-function #'ignore
- ;; enable recursive minibuffers
- enable-recursive-minibuffers t
- ;; save bookmarks immediately
- bookmark-save-flag 1
- ;; make some space in the Packages menu
- package-archive-column-width 12
- package-version-column-width 28
- ;; select some packages to install
- package-selected-packages '(
-                             all-the-icons
-                             all-the-icons-completion
-                             all-the-icons-dired
-                             all-the-icons-gnus
-                             auctex
-                             auctex-latexmk
-                             avy
-                             bbdb
-                             corfu
-                             define-word
-                             devdocs
-                             diff-hl
-                             elfeed
-                             embark-consult
-                             emms
-                             gnu-elpa-keyring-update
-                             gnuplot
-                             graphql-mode
-                             graphviz-dot-mode
-                             haskell-mode
-                             htmlize
-                             ialign
-                             keycast
-                             kubernetes
-                             lin
-                             magit
-                             marginalia
-                             markdown-mode
-                             mastodon
-                             ob-prolog
-                             orderless
-                             org
-                             org-modern
-                             package-lint
-                             paredit
-                             pdf-tools
-                             rainbow-delimiters
-                             rainbow-mode
-                             request
-                             rg
-                             smtpmail-multi
-                             sqlformat
-                             sweeprolog
-                             terraform-mode
-                             vterm
-                             vundo
-                             whitespace-cleanup-mode
-                             with-editor
-                             )
- ;; configure Org capture templates
- org-capture-templates '(("t" "Todo [inbox]" entry
-                          (file+headline "~/org/inbox.org" "Tasks")
-                          "** TODO [#B] %^{Task}    %^g
+(defvar esy/init-step-times nil)
+
+(defmacro esy/init-step (name _doc &rest body)
+  (declare (indent defun)
+           (doc-string 2))
+  (let ((start-time-symbol (gensym)))
+    `(let ((,start-time-symbol (current-time)))
+       ,@body
+       (push (cons ',name
+                   (time-subtract (current-time) ,start-time-symbol))
+             esy/init-step-times))))
+
+(esy/init-step gc
+  "Temporarily increase GC threshold to expedite Emacs startup."
+  (let ((normal-gc-cons-threshold (* 20 1024 1024))
+        (init-gc-cons-threshold (* 1024 1024 1024))
+        (normal-mode-line-format mode-line-format))
+    (setq gc-cons-threshold init-gc-cons-threshold
+          mode-line-format nil)
+    (add-hook 'after-init-hook
+              (lambda () (setq gc-cons-threshold normal-gc-cons-threshold
+                               mode-line-format normal-mode-line-format)))))
+
+(esy/init-step vars
+  "Set some variables."
+  (setq
+   ;; my name
+   user-full-name "Eshel Yaron"
+   ;; my email address
+   user-mail-address "me@eshelyaron.com"
+   ;; direct Custom definitions to some file and forget about it
+   custom-file (expand-file-name "custom.el" user-emacs-directory)
+   ;; silence native compilation warnings
+   native-comp-async-report-warnings-errors 'silent
+   ;; only display the warning buffer if something's really broken
+   warning-minimum-level :error
+   ;; don't use stale .elc files
+   load-prefer-newer t
+   ;; maximize Emacs on startup
+   default-frame-alist '((fullscreen . fullboth))
+   ;; disable popup dialogs
+   use-dialog-box nil
+   ;; disable splash screen
+   inhibit-startup-screen t
+   ;; make the scratch message more concise
+   initial-scratch-message ";; Go.\n"
+   ;; don't ring the bell
+   ring-bell-function #'ignore
+   ;; make C-x b obey display-buffer-alist
+   switch-to-buffer-obey-display-actions t
+   ;; disable new mail mode line indication
+   display-time-mail-function #'ignore
+   ;; enable recursive minibuffers
+   enable-recursive-minibuffers t
+   ;; save bookmarks immediately
+   bookmark-save-flag 1
+   ;; make some space in the Packages menu
+   package-archive-column-width 12
+   package-version-column-width 28
+   ;; configure package archives
+   package-archives '(("melpa"  . "http://melpa.org/packages/")
+                      ("gnu"    . "https://elpa.gnu.org/devel/")
+                      ("nongnu" . "https://elpa.nongnu.org/nongnu-devel/"))
+   ;; select some packages to install
+   package-selected-packages '(
+                               all-the-icons
+                               all-the-icons-completion
+                               all-the-icons-dired
+                               all-the-icons-gnus
+                               auctex
+                               avy
+                               bbdb
+                               corfu
+                               define-word
+                               devdocs
+                               diff-hl
+                               elfeed
+                               embark-consult
+                               emms
+                               gnu-elpa-keyring-update
+                               gnuplot
+                               graphql-mode
+                               graphviz-dot-mode
+                               htmlize
+                               ialign
+                               keycast
+                               kubernetes
+                               lin
+                               magit
+                               marginalia
+                               markdown-mode
+                               mastodon
+                               ob-prolog
+                               orderless
+                               org
+                               org-modern
+                               package-lint
+                               paredit
+                               pdf-tools
+                               rainbow-delimiters
+                               rainbow-mode
+                               rg
+                               smtpmail-multi
+                               sqlformat
+                               sweeprolog
+                               terraform-mode
+                               vterm
+                               vundo
+                               whitespace-cleanup-mode
+                               )
+   ;; configure Org capture templates
+   org-capture-templates '(("t" "Todo [inbox]" entry
+                            (file+headline "~/org/inbox.org" "Tasks")
+                            "** TODO [#B] %^{Task}    %^g
 :PROPERTIES:
 :CreatedAt: %u
 :CapturedAt: %a
 :CapturedAs: Inbox Task
 :END:"
-                          :prepend t
-                          :empty-lines 1
-                          :immediate-finish t)
-                         ("w" "Work [inbox]" entry
-                          (file+headline "~/org/inbox.org" "Tasks")
-                          "** TODO [#B] %^{Task}    :work:
+                            :prepend t
+                            :empty-lines 1
+                            :immediate-finish t)
+                           ("w" "Work [inbox]" entry
+                            (file+headline "~/org/inbox.org" "Tasks")
+                            "** TODO [#B] %^{Task}    :work:
 :PROPERTIES:
 :CreatedAt: %u
 :CapturedAt: %a
 :CapturedAs: Work Task
 :END:"
-                          :prepend t
-                          :empty-lines 1
-                          :immediate-finish t)
-                         ("c" "New Calendar Event" entry
-                          (file+headline "~/org/inbox.org" "Calendar")
-                          "** %^{Title}    %^g
+                            :prepend t
+                            :empty-lines 1
+                            :immediate-finish t)
+                           ("c" "New Calendar Event" entry
+                            (file+headline "~/org/inbox.org" "Calendar")
+                            "** %^{Title}    %^g
 :PROPERTIES:
 :CreatedAt: %u
 :CapturedAt: %a
 :END:
 %(format-time-string \"<%Y-%m-%d %H:%M\" (org-read-date t t))-%(format-time-string \"%H:%M>\" (org-read-date t t))
 %i"
-                          :prepend t
-                          :empty-lines 1
-                          :immediate-finish t))
- ;; point Org to file agenda file
- org-agenda-files '("~/org/inbox.org")
- org-default-notes-file "~/org/inbox.org"
- ;; weeks start on Sunday
- org-agenda-start-on-weekday 0
- ;; use a nice unicode ellipsis
- org-ellipsis "…"
- ;; open everything withing Emacs
- org-file-apps '((t . emacs))
- ;; enable Org speed commands
- org-use-speed-commands t
- ;; task states
- org-todo-keywords '((sequence
-                      "TODO(t)"
-                      "BLOCKED(b@/!)"
-                      "INPROGRESS(i!)"
-                      "|"
-                      "DONE(d!)"
-                      "CANCELED(c@)"))
- ;; set a task to INPROGRESS when I clock into it
- org-clock-in-switch-to-state "INPROGRESS"
- ;; log the finish time of tasks in Org
- org-log-done 'time
- org-log-into-drawer t
- ;; minimal prompt for task states
- org-use-fast-todo-selection 'expert
- ;; set up a group of mutual exclusive tags
- org-tag-alist '((:startgroup)
-                 ("work"     . ?w)
-                 ("studies"  . ?s)
-                 ("esols"    . ?e)
-                 ("personal" . ?p)
-                 (:endgroup))
- ;; minimal prompt for tags
- org-fast-tag-selection-single-key 'expert
- ;; allow evaluating some languages with Org Babel
- org-babel-load-languages '((emacs-lisp . t)
-                            (shell      . t)
-                            (sql        . t)
-                            (prolog     . t))
- ;; do not ask for confirmation when evaluating Org source blocks
- org-confirm-babel-evaluate nil
- ;; extra modules to load along with Org
- org-modules '(ol-bbdb
-               ol-bibtex
-               ol-docview
-               ol-gnus
-               ol-info
-               ol-irc
-               ol-mhe
-               ol-rmail
-               ol-eww
-               ob-sql
-               org-tempo)
- ;; refile Org entries to my agenda file(s)
- org-refile-targets '((org-agenda-files . (:maxlevel . 5))
-                      (nil . (:maxlevel . 3)))
- ;; use full outline path when prompting for refile target
- org-refile-use-outline-path t
- ;; archive for Org entries
- org-archive-location "~/org/journal.org::datetree/* Finished Tasks   :ARCHIVE:"
- ;; enable italic text in code
- modus-themes-italic-constructs t
- ;; enable bold text in code
- modus-themes-bold-constructs t
- ;; use fixed-pitch text where appropriate
- modus-themes-mixed-fonts t
- ;; make prompts bold
- modus-themes-prompts '(bold)
- ;; give source blocks a distinct background shade
- modus-themes-org-blocks 'gray-background
- ;; use variable-pitch text here are there
- modus-themes-variable-pitch-ui t
- ;; fine-tune some theme settings
- modus-themes-common-palette-overrides '((border-mode-line-active unspecified)
-                                         (border-mode-line-inactive unspecified)
-                                         (bg-mode-line-active bg-blue-intense)
-                                         (fg-mode-line-active fg-main)
-                                         (fringe unspecified)
-                                         (underline-link border)
-                                         (underline-link-visited border)
-                                         (underline-link-symbolic border)
-                                         (fg-region unspecified))
- ;; follow links to version-controlled files without confirming
- vc-follow-symlinks t
- ;; jump from .c to .h files with find-sibling-file
- find-sibling-rules '(("\\([^/]+\\)\\.c\\'" "\\1.h"))
- ;; configure multiple SMTP accounts
- smtpmail-multi-accounts '((esy . ("eshelshay.yaron@gmail.com"
-                                   "smtp.gmail.com" 587
-                                   "eshelshay.yaron@gmail.com"
-                                   starttls nil nil nil))
-                           (swp . ("eshel@swi-prolog.org"
-                                   "mail.swi-prolog.com" 587
-                                   "eshel@swi-prolog.org"
-                                   starttls nil nil nil))
-                           (me  . ("me@eshelyaron.com"
-                                   "mail.eshelyaron.com"
-                                   587
-                                   "me@eshelyaron.com"
-                                   starttls nil nil nil)))
- ;; associate email addresses with SMTP accounts
- smtpmail-multi-associations '(("[^z-a]*eshelshay\\.yaron@gmail\\.com[^z-a]*" esy)
-                               ("[^z-a]*eshel@swi-prolog\\.org[^z-a]*"        swp)
-                               ("[^z-a]*me@eshelyaron\\.com[^z-a]*"           me))
- ;; set function for sending email
- send-mail-function #'smtpmail-multi-send-it
- message-send-mail-function #'smtpmail-multi-send-it
- ;; set email user agent
- mail-user-agent 'gnus-user-agent
- ;; make Gnus not prompt as much
- gnus-expert-user t
- ;; make Gnus read the dribble file on startup without prompting
- gnus-always-read-dribble-file t
- ;; don't break pages in incoming email
- gnus-break-pages nil
- ;; don't show the Gnus startup screen
- gnus-inhibit-startup-message t
- ;; set Gnus backends
- gnus-select-method '(nntp "news.gmane.io")
- gnus-secondary-select-methods '((nnimap "gmail"
-                                         (nnimap-address "imap.gmail.com")
-                                         (nnimap-server-port "imaps")
-                                         (nnimap-stream ssl))
-                                 (nnimap "me"
-                                         (nnimap-address "mail.eshelyaron.com")
-                                         (nnimap-server-port "imaps")
-                                         (nnimap-stream ssl))
-                                 (nnimap "swi"
-                                         (nnimap-address "mail.swi-prolog.com")
-                                         (nnimap-server-port "imaps")
-                                         (nnimap-stream ssl)))
- ;; simpler message
- gnus-no-groups-message "No new articles"
- ;; don't close other windows when opening Gnus
- gnus-use-full-window nil
- ;; make Gnus handle this content types
- gnus-article-treat-types '("text/plain"
-                            "text/x-verbatim"
-                            "text/x-patch"
-                            "text/html"
-                            "text/calendar")
- ;; capture calendar events from Gnus to my Org inbox file
- gnus-icalendar-org-capture-file "~/org/inbox.org"
- ;; put calendar events under the Calendar heading
- gnus-icalendar-org-capture-headline '("Calendar")
- ;; use ISO format for calendar dates
- calendar-date-style 'iso
- ;; maintain legibility of rendered text in HTML mails
- shr-color-visible-luminance-min 75
- ;; my Mastodon settings
- mastodon-instance-url "https://emacs.ch"
- mastodon-active-user "eshel"
- ;; direct Magit to my Git checkouts directory
- magit-repository-directories '(("~/checkouts/" . 1))
- ;; have Dired operations target another visible Dired buffer
- dired-dwim-target t
- ;; use zsh in vterm by default
- vterm-shell "/bin/zsh"
- vterm-max-scrollback 2048
- vterm-kill-buffer-on-exit nil
- vterm-use-vterm-prompt-detection-method t
- ;; use MPV with EMMS
- emms-player-list '(emms-player-mpv)
- bbdb-phone-style nil
- eww-auto-rename-buffer 'title
- browse-url-browser-function #'eww-browse-url
- browse-url-generic-program "open"
- ;; set up some feeds for Elfeed
- elfeed-feeds '(
-                ("http://node2.feed43.com/7487052648530856.xml" comics scifi)
-                ("https://ajroach42.com/feed.xml")
-                ("https://alexschroeder.ch/wiki/feed/full/" tech)
-                ("https://amodernist.com/all.atom" emacs)
-                ("https://archive.casouri.cc/note/atom.xml")
-                ("https://arcology.garden/updates.xml")
-                ("http://www.aaronsw.com/weblog/index.xml" blog tech)
-                ("https://atthis.link/rss.xml" tech)
-                ("https://daily.baty.net/feed/feed.xml" blog tech)
-                ("https://baty.net/feed" tech)
-                ("https://betterappsec.com/feed")
-                ("https://bitspook.in/blog/feed.xml")
-                ("https://blog.acthompson.net/feeds/posts/default")
-                ("https://bruda.ca/feed.php")
-                ("https://cce.whatthefuck.computer/updates.xml")
-                ("https://cdn.jwz.org/blog/feed/" tech)
-                ("https://cestlaz.github.io/rss.xml" tech)
-                ("https://changelog.complete.org/feed")
-                ("https://daniel.haxx.se/blog/feed/" tech)
-                ("https://drewdevault.com/blog/index.xml")
-                ("https://emacs.dyerdwelling.family/index.xml")
-                ("https://emacsninja.com/feed.atom" emacs)
-                ("https://erikmcclure.com/blog/index.xml")
-                ("https://evanhahn.com/blog/index.xml")
-                ("https://fasterthanli.me/index.xml")
-                ("https://feeds.buzzsprout.com/2134279.rss")
-                ("https://flower.codes/feed.xml")
-                ("https://garymarcus.substack.com/feed")
-                ("https://gwern.substack.com/feed" tech blog)
-                ("https://herman.bearblog.dev/feed/")
-                ("https://irreal.org/blog/?feed=rss2" emacs)
-                ("https://jcm.libsyn.com/rss/")
-                ("https://jnd.org/feed" tech philosophy)
-                ("https://kelar.org/~bandali/atom.xml" tech blog)
-                ("https://kitchen-sink.kwakk.info/feed" comics)
-                ("https://lars.ingebrigtsen.no/feed" emacs)
-                ("https://lwn.net/headlines/rss" linux)
-                ("https://maggieappleton.com/rss.xml" design)
-                ("https://matklad.github.io/feed.xml")
-                ("https://matt-rickard.com/rss" tech)
-                ("https://njoseph.me/shaarli/feed/atom?")
-                ("https://nullprogram.com/feed/" tech)
-                ("https://olddeuteronomy.github.io/index.xml")
-                ("https://parasurv.neocities.org/rss.xml" tech blog)
-                ("https://phaazon.net/blog/feed")
-                ("https://planet.emacslife.com/atom.xml" emacs)
-                ("https://ploum.net/atom_en.xml")
-                ("https://pod.fossified.com/podcast.rss" tech podcast)
-                ("https://pouria.dev/rss.xml")
-                ("https://project-mage.org/rss.xml")
-                ("https://protesilaos.com/master.xml" emacs philosophy)
-                ("https://reddit.com/r/prolog/.rss" prolog)
-                ("https://risky.biz/rss.xml" tech)
-                ("https://sachachua.com/blog/feed/" emacs)
-                ("https://stephanango.com/feed.xml")
-                ("https://stppodcast.libsyn.com/rss")
-                ("https://takeonrules.com/index.atom")
-                ("https://two-wrongs.com/feed")
-                ("https://typeclasses.substack.com/feed")
-                ("https://unixsheikh.com/feed.rss" linux)
-                ("https://usesthis.com/feed.atom")
-                ("https://writer13.neocities.org/rss.xml")
-                ("https://www.cs.uni.edu/~wallingf/blog/atom.xml")
-                ("https://www.devever.net/~hl/index.feed" tech)
-                ("https://www.draketo.de/rss-feed.xml")
-                ("https://www.evalapply.org/index.xml" emacs)
-                ("https://www.fsf.org/static/fsforg/rss/news.xml" tech)
-                ("https://www.haskellforall.com/feeds/posts/default" tech)
-                ("https://www.murilopereira.com/index.xml")
-                ("https://www.stilldrinking.org/rss/feed.xml")
-                ("https://www.theregister.com/headlines.atom" tech)
-                ("https://www.typetheoryforall.com/episodes.mp3.rss" podcast)
-                ("https://xeiaso.net/blog.rss" tech)
-                ("https://xenodium.com/rss.xml" emacs)
-                ("https://xkcd.com/rss.xml" comics tech)
-                )
- ;; don't use bold face for unread Elfeed entries
- elfeed-search-face-alist '((unread (default)))
- global-auto-revert-non-file-buffers t
- auto-revert-verbose nil
- query-about-changed-file t
- kill-do-not-save-duplicates t
- show-trailing-whitespace t
- read-extended-command-predicate #'command-completion-default-include-p
- completions-format 'one-column
- completion-auto-select nil
- completions-detailed nil
- completion-styles '(orderless partial-completion basic)
- completion-show-help nil
- completions-header-format (propertize "%s candidates:\n" 'face 'shadow)
- completion-auto-help 'visual
- completions-max-height 16
- completion-auto-wrap t
- corfu-cycle t
- corfu-margin-formatters '(esy/margin-formatter)
- corfu-indexed-start     1
- shell-kill-buffer-on-exit t
- ;; allow disabling confirming before compilation via local variables
- safe-local-variable-values '((compilation-read-command . nil))
- TeX-view-program-selection '((output-pdf "PDF Tools"))
- TeX-source-correlate-start-server t
- xref-show-definitions-function #'consult-xref
- xref-show-xrefs-function       #'consult-xref
- xref-search-program             'ripgrep
- sqlformat-command 'pgformatter
- sql-input-ring-file-name (expand-file-name ".sqli-history" user-emacs-directory)
- ;; use relative line numbers
- display-line-numbers-type 'relative
- ;; persist Git commit message history
- savehist-additional-variables '(log-edit-comment-ring)
- )
-(esy/init-step 'vars)
-
-;;;; load the modus-vivendi theme
-(require-theme 'modus-themes)
-(load-theme 'modus-vivendi)
-(esy/init-step 'theme)
-
-;;;; set up the Iosevka font family
-(set-face-attribute 'default nil        :family "Iosevka" :height 130)
-(set-face-attribute 'fixed-pitch nil    :family "Iosevka")
-(set-face-attribute 'variable-pitch nil :family "Iosevka Etoile")
-(esy/init-step 'fonts)
-
-(add-to-list 'load-path (expand-file-name "lisp/" user-emacs-directory))
-
-(setq-default indent-tabs-mode nil)
-
-(defvar-keymap transpose-lines-repeat-map
-  :doc "Repeat map for \\[transpose-lines]"
-  "C-t" #'transpose-lines)
-
-(put 'transpose-lines 'repeat-map 'transpose-lines-repeat-map)
-
-;;;; utility commands
-(defun esy/kill-dwim ()
-  "When region is active, kill region, otherwise kill last word."
-  (interactive)
-  (if (region-active-p)
-      (let ((beg (region-beginning))
-            (end (region-end)))
-        (kill-region beg end))
-    (let ((end (point)))
-      (backward-word)
-      (kill-region (point) end))))
-
-(defun esy/pulse-line (&optional _)
-  "Pulse current line."
-  (interactive)
-  (pulse-momentary-highlight-one-line))
-
-(defun esy/seconds-to-date-string (seconds)
-  "Decode and display the date corresponding to SECONDS."
-  (interactive (list (if (use-region-p)
-                         (string-to-number (buffer-substring (region-beginning)
-                                                             (region-end)))
-                       (read-number "Unix timestamp: " (round (float-time))))))
-  (message (format-time-string "%FT%T%z" (seconds-to-time seconds))))
-
-(defun esy/find-init-file (init)
-  "Find the Emacs INIT file."
-  (interactive (list user-init-file))
-  (find-file init))
-
-(defun esy/eww-target ()
-  (require 'eww)
-  (if (use-region-p)
-      (let ((target (buffer-substring-no-properties (use-region-beginning)
-                                                    (use-region-end))))
-        (add-to-history 'eww-prompt-history target)
-        target)
-    (completing-read "Browse or search: "
-                     eww-prompt-history nil nil nil
-                     'eww-prompt-history
-                     (eww-suggested-uris))))
-
-(defun esy/eww (target)
-  "Browse or search for TARGET."
-  (interactive (list (esy/eww-target)))
-  (eww target))
-
-(with-eval-after-load 'shell
-  (keymap-set shell-mode-map "SPC" #'comint-magic-space))
-
-(defvar-local esy/log-buffer-filename nil
-  "File in which the current buffer is logged.")
-
-(defun esy/log-buffer ()
-  "Save the current buffer under the ~/logs/ directory."
-  (interactive)
-  (let ((filename (or esy/log-buffer-filename
-                      (setq esy/log-buffer-filename
-                            (expand-file-name
-                             (concat mode-name
-                                     "_"
-                                     (format-time-string
-                                      "%Y%m%d%H%M%S"
-                                      (current-time))
-                                     ".log")
-                             "~/logs")))))
-    (save-restriction
-      (widen)
-      (write-region (point-min)
-                    (point-max)
-                    filename))))
-
-(with-eval-after-load 'comint
-  (keymap-set comint-mode-map "C-c C-q" #'esy/log-buffer))
-
-;;;; override the default startup message
-(define-advice startup-echo-area-message (:override () report-init-time)
-  (format "%s started in %s.  Hack away."
-          (propertize "Emacs"           'face 'success)
-          (propertize (emacs-init-time) 'face 'error  )))
-
-(esy/init-step 'stuff)
-
-;;;; set up external packages
-(require 'package)
-
-;;;; configure non-default package archive
-(add-to-list 'package-archives
-             '("melpa" . "http://melpa.org/packages/"))
-(add-to-list 'package-archives
-             '("elpa-devel" . "https://elpa.gnu.org/devel/"))
-
-;;;; install packages
-(package-install-selected-packages)
-
-(esy/init-step 'packages)
-
-;;;; enable some built-in tree-sitter modes
-(add-to-list 'auto-mode-alist '("Dockerfile"  . dockerfile-ts-mode))
-(add-to-list 'auto-mode-alist '("\\.ya?ml\\'" . yaml-ts-mode))
-(add-to-list 'auto-mode-alist '("\\.json\\'"  . json-ts-mode))
-(add-to-list 'auto-mode-alist '("\\.rs\\'"    . rust-ts-mode))
-(add-to-list 'auto-mode-alist '("\\.go\\'"    . go-ts-mode))
-
-;;;; enable spell checking in text buffers
-(add-hook 'text-mode-hook #'flyspell-mode)
-
-(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
-(add-hook 'prog-mode-hook #'display-line-numbers-mode)
-(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode)
-(add-hook 'prog-mode-hook #'flyspell-prog-mode)
-(add-hook 'prog-mode-hook #'flymake-mode)
-(esy/init-step 'prog-mode-hooks)
-
-;;;; bind some keys
-(keymap-global-set "C-c w" #'esy/eww)
-(keymap-global-set "C-c c" #'org-capture)
-(keymap-global-set "C-c l" #'org-store-link)
-(keymap-global-set "C-c a" #'org-agenda)
-(keymap-global-set "C-c !" #'consult-flymake)
-(keymap-global-set "C-c C" #'openai-chat)
-(keymap-global-set "C-c E" #'elfeed)
-(keymap-global-set "C-c G" #'gnus)
-(keymap-global-set "C-c M" #'mastodon)
-(keymap-global-set "C-c V" #'vertalen-at-point)
-(keymap-global-set "C-c F" #'esy/find-init-file)
-(keymap-global-set "C-c P" #'list-packages)
-(keymap-global-set "C-c SPC" #'consult-mark)
-(keymap-global-set "<remap> <kill-region>" #'esy/kill-dwim)
-(keymap-global-set "<remap> <goto-line>" #'consult-goto-line)
-(keymap-global-set "<remap> <suspend-frame>" #'zap-up-to-char)
-(keymap-global-set "<remap> <imenu>" #'consult-imenu)
-(keymap-global-set "<remap> <make-frame>" #'duplicate-line)
-(keymap-global-set "M-#" #'define-word-at-point)
-(keymap-global-set "M-o" #'previous-buffer)
-(keymap-global-set "M-O" #'next-buffer)
-(keymap-global-set "C-," #'backward-delete-char)
-(keymap-global-set "C-." #'embark-act)
-(keymap-global-set "C-;" #'avy-goto-char-timer)
-(keymap-global-set "C-s-f" #'toggle-frame-fullscreen)
-(keymap-global-set "C-s-l" #'esy/pulse-line)
-
-(keymap-set ctl-x-map "b" #'consult-buffer)
-(keymap-set ctl-x-4-map "b" #'consult-buffer-other-window)
-(keymap-set ctl-x-5-map "b" #'consult-buffer-other-frame)
-(keymap-set ctl-x-map "r b" #'consult-bookmark)
-(keymap-set ctl-x-map "C-b" #'ibuffer)
-
-(keymap-set search-map "r" #'rg)
-(keymap-set search-map "l" #'rg-literal)
-(keymap-set search-map "b" #'some-button)
-(esy/init-step 'bindings)
-
-;;;; unbind some keys
-(dolist (key '("s-a" "s-d" "s-e" "s-f" "s-g" "s-h" "s-j" "s-k" "s-l"
-               "s-m" "s-o" "s-p" "s-q" "s-s" "s-t" "s-u" "s-w" "s-x"
-               "s-y" "s-z"))
-  (keymap-global-unset key))
-
-;;;; enable some commands
-(dolist (command '(set-goal-column
-                   narrow-to-region
-                   narrow-to-page))
-  (put command 'disabled nil))
-
-;;;; disable some commands
-(put 'suspend-frame 'disabled t)
-
-(with-eval-after-load 'project
-  (define-advice project--find-in-directory (:override (dir) no-remote-projects)
-    (unless (file-remote-p dir)
-      (run-hook-with-args-until-success 'project-find-functions dir)))
-  (add-to-list 'project-switch-commands '(project-compile "Compile"))
-  (add-to-list 'project-switch-commands '(rg-project "rg"))
-  (add-to-list 'project-switch-commands '(magit-project-status "Magit"))
-  (add-to-list 'project-switch-commands '(project-shell "Shell"))
-  (when (boundp 'project-prefix-map)
-    (define-key project-prefix-map "R" #'rg-project)
-    (define-key project-prefix-map "m" #'magit-project-status)))
-
-;;;; SQL servers
-(with-eval-after-load 'sql
-  (setq sql-connection-alist (let* ((a (auth-source-search :port 5432
-                                                           :max  6
-                                                           :require '(:user :port :secret :host)))
-                                    (d (nth 0 a))
-                                    (p (nth 1 a))
-                                    (c (nth 2 a))
-                                    (e (nth 3 a))
-                                    (f (nth 4 a))
-                                    (i (nth 5 a)))
-                               `((dev
-                                  (sql-product 'postgres)
-                                  (sql-user ,(plist-get d :user))
-                                  (sql-port 5432)
-                                  (sql-password ,(funcall (plist-get d :secret)))
-                                  (sql-server ,(plist-get d :host))
-                                  (sql-database "alerts"))
-                                 (prod
-                                  (sql-product 'postgres)
-                                  (sql-user ,(plist-get p :user))
-                                  (sql-port 5432)
-                                  (sql-password ,(funcall (plist-get p :secret)))
-                                  (sql-server ,(plist-get p :host))
-                                  (sql-database "alerts"))
-                                 (cgs
-                                  (sql-product 'postgres)
-                                  (sql-user ,(plist-get c :user))
-                                  (sql-port 5432)
-                                  (sql-password ,(funcall (plist-get c :secret)))
-                                  (sql-server ,(plist-get c :host))
-                                  (sql-database "container_graph"))
-                                 (ten
-                                  (sql-product 'postgres)
-                                  (sql-user ,(plist-get e :user))
-                                  (sql-port 5432)
-                                  (sql-password ,(funcall (plist-get e :secret)))
-                                  (sql-server ,(cadr (split-string (plist-get e :host)  (rx "^"))))
-                                  (sql-database ,(car (split-string (plist-get e :host) (rx "^")))))
-                                 (ac
-                                  (sql-product 'postgres)
-                                  (sql-user ,(plist-get f :user))
-                                  (sql-port 5432)
-                                  (sql-password ,(funcall (plist-get f :secret)))
-                                  (sql-server ,(cadr (split-string (plist-get f :host)  (rx "^"))))
-                                  (sql-database ,(car (split-string (plist-get f :host) (rx "^")))))
-                                 (int
-                                  (sql-product 'postgres)
-                                  (sql-user ,(plist-get i :user))
-                                  (sql-port 5432)
-                                  (sql-password ,(funcall (plist-get i :secret)))
-                                  (sql-server ,(cadr (split-string (plist-get i :host)  (rx "^"))))
-                                  (sql-database ,(car (split-string (plist-get i :host) (rx "^"))))))))
-  (add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
-  (define-key sql-mode-map (kbd "C-c C-f") #'sqlformat)
-  (define-advice sql-comint-postgres (:around (fun &rest args) use-sql-password)
-    (let ((process-environment
-           (nconc
-            (list (format "PGPASSWORD=%s" sql-password))
-            process-environment)))
-      (apply fun args))))
-
-;;;; Org settings
-(with-eval-after-load 'org
-  (keymap-unset org-mode-map "C-," t))
-
-(with-eval-after-load 'org-agenda
-  (add-to-list 'org-agenda-custom-commands
-               '("w" "Work TODOs" tags-todo "+work")))
-
-;;;; mail settings
-(with-eval-after-load 'gnus
-  (require 'gnus-icalendar)
-  (add-hook 'gnus-group-mode-hook #'gnus-topic-mode)
-  (gnus-icalendar-setup)
-  (gnus-icalendar-org-setup)
-  (all-the-icons-gnus-setup)
-  (bbdb-initialize 'gnus 'mail 'message))
-
-(defun esy/local-all-the-icons-dired-mode ()
-  (unless (file-remote-p default-directory)
-    (all-the-icons-dired-mode)))
-
-(with-eval-after-load 'dired
-  (put 'dired-find-alternate-file 'disabled nil)
-  (add-hook 'dired-mode-hook #'esy/local-all-the-icons-dired-mode))
-
-;;;; workaround for Bug#62329
-;; (with-eval-after-load 'compile
-;;   (require 'tramp-sh))
-
-(with-eval-after-load 'tramp
-  (tramp-set-completion-function "ssh" '((tramp-parse-netrc "~/.authinfo.gpg")))
-  (add-to-list 'backup-directory-alist (cons tramp-file-name-regexp nil)))
-
-(with-eval-after-load 'proced
-  (add-hook 'proced-mode-hook #'proced-toggle-auto-update))
-
-(with-eval-after-load 'time
-  (add-to-list 'zoneinfo-style-world-list '("Europe/Amsterdam" "Amsterdam")))
-
-(with-eval-after-load 'flyspell
-  (keymap-unset flyspell-mode-map "C-," t)
-  (keymap-unset flyspell-mode-map "C-." t)
-  (keymap-unset flyspell-mode-map "C-;" t))
-
-(add-to-list 'display-buffer-alist
-             '("\\*Completions\\*"
-               (display-buffer-reuse-window display-buffer-at-bottom)
-               (window-parameters . ((mode-line-format . none)))))
-
-(add-hook 'completion-list-mode-hook
-          (lambda ()
-            (setq-local cursor-in-non-selected-windows nil)))
-
-(define-advice completion-file-name-table (:filter-args (args) no-remote-file-name-completion)
-  (let ((string (car args))
-        (pred (cadr args))
-        (action (caddr args)))
-    (if (and (file-remote-p string) (eq pred #'file-exists-p))
-        (list string nil action)
-      (list string pred action))))
-
-(define-key minibuffer-local-completion-map
-            [remap previous-line] #'minibuffer-previous-completion)
-(define-key minibuffer-local-completion-map
-            [remap next-line]     #'minibuffer-next-completion)
-
-(defun esy/dabbrev-capf ()
-  "Workaround for issue with `dabbrev-capf'."
-  (require 'dabbrev)
-  (dabbrev--reset-global-variables)
-  (setq dabbrev-case-fold-search nil)
-  (dabbrev-capf))
-
-(add-to-list 'completion-at-point-functions #'esy/dabbrev-capf)
-
-(defun esy/file-capf ()
-  "File completion at point function."
-  (let ((bs (bounds-of-thing-at-point 'filename)))
-    (when bs
-      (let* ((start (car bs))
-             (end   (cdr bs)))
-        `(,start ,end completion--file-name-table . (:exclusive no))))))
-
-(add-to-list 'completion-at-point-functions #'esy/file-capf)
-(esy/init-step 'capf)
-
-(require 'use-package)
-(esy/init-step 'use-package)
-;;;; highlight the current line in line-oriented buffers
-(use-package lin
-  :config
-  (add-to-list 'lin-mode-hooks 'gnus-summary-mode-hook)
-  (add-to-list 'lin-mode-hooks 'gnus-group-mode-hook)
-  (add-to-list 'lin-mode-hooks 'gnus-server-mode-hook))
-(esy/init-step 'lin)
-
-(use-package all-the-icons
-  :config
-  (add-to-list 'all-the-icons-extension-icon-alist
-               '("pl" all-the-icons-alltheicon "prolog"
-                 :height 1.1 :face all-the-icons-lmaroon)))
-(esy/init-step 'icons)
-
-(defun esy/margin-formatter (metadata)
-  "Format METADATA for `corfu-margin-formatters'."
-  (pcase (cdr (assoc 'category metadata))
-    ('file (lambda (string)
-             (concat (if (string-suffix-p "/" string)
-                         (all-the-icons-icon-for-dir string)
-                       (all-the-icons-icon-for-file string))
-                     " ")))
-    ('dabbrev (lambda (_) "… "))))
-
-(use-package marginalia
-  :config
-  (add-hook 'marginalia-mode-hook #'all-the-icons-completion-marginalia-setup))
-(esy/init-step 'marginalia)
-
-;; enable some global minor modes
-(dolist-with-progress-reporter (mode '(
-                                       column-number-mode
-                                       context-menu-mode
-                                       corfu-indexed-mode
-                                       display-time-mode
-                                       display-battery-mode
-                                       global-auto-revert-mode
-                                       global-corfu-mode
-                                       global-diff-hl-mode
-                                       global-whitespace-cleanup-mode
-                                       lin-global-mode
-                                       marginalia-mode
-                                       minibuffer-depth-indicate-mode
-                                       mouse-avoidance-mode
-                                       pixel-scroll-precision-mode
-                                       recentf-mode
-                                       repeat-mode
-                                       save-place-mode
-                                       savehist-mode
-                                       show-paren-mode
-                                       transient-mark-mode
-                                       ))
-    "Enabling minor modes"
-  (funcall mode))
-(esy/init-step 'minor-modes)
-
-(emms-minimalistic)
-(esy/init-step 'emms)
-
-;;;; PDF settings
-(use-package pdf-tools
-  :mode  ("\\.pdf\\'" . pdf-view-mode)
-  :config
-  (pdf-tools-install :no-query)
-  (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode)
-  (add-to-list 'revert-without-query "\\.pdf\\'"))
-(esy/init-step 'pdf)
-
-(with-eval-after-load 'tex
-  (add-hook 'TeX-after-compilation-finished-functions
-            #'TeX-revert-document-buffer))
-(esy/init-step 'tex)
-
-(with-eval-after-load 'vterm
-  (add-to-list 'vterm-tramp-shells '("kubernetes" "/bin/bash")))
-;; (use-package vterm
-;;   :config
-;;   (add-to-list 'vterm-tramp-shells '("kubernetes" "/bin/bash")))
-(esy/init-step 'vterm)
-
-(use-package paredit
-  :hook lisp-data-mode
-  :config
-  (keymap-unset paredit-mode-map "M-s" t)
-  (keymap-unset paredit-mode-map "M-?" t))
-(esy/init-step 'paredit)
-
-(use-package haskell-mode
-  :hook ((haskell-mode . interactive-haskell-mode)
-         (haskell-mode . haskell-decl-scan-mode)
-         (haskell-mode . haskell-doc-mode)))
-(esy/init-step 'haskell)
-
-(use-package sweeprolog
-  :mode ("\\.plt?\\'" . sweeprolog-mode)
-  :bind-keymap ("C-c p" . sweeprolog-prefix-map)
-  :config
-  (add-hook 'sweeprolog-mode-hook #'sweeprolog-electric-layout-mode)
-  (add-hook 'sweeprolog-top-level-mode-hook #'compilation-shell-minor-mode)
+                            :prepend t
+                            :empty-lines 1
+                            :immediate-finish t))
+   ;; point Org to file agenda file
+   org-agenda-files '("~/org/inbox.org")
+   org-default-notes-file "~/org/inbox.org"
+   ;; weeks start on Sunday
+   org-agenda-start-on-weekday 0
+   ;; use a nice unicode ellipsis
+   org-ellipsis "…"
+   ;; open everything withing Emacs
+   org-file-apps '((t . emacs))
+   ;; enable Org speed commands
+   org-use-speed-commands t
+   ;; task states
+   org-todo-keywords '((sequence
+                        "TODO(t)"
+                        "BLOCKED(b@/!)"
+                        "INPROGRESS(i!)"
+                        "|"
+                        "DONE(d!)"
+                        "CANCELED(c@)"))
+   ;; set a task to INPROGRESS when I clock into it
+   org-clock-in-switch-to-state "INPROGRESS"
+   ;; log the finish time of tasks in Org
+   org-log-done 'time
+   org-log-into-drawer t
+   ;; minimal prompt for task states
+   org-use-fast-todo-selection 'expert
+   ;; set up a group of mutual exclusive tags
+   org-tag-alist '((:startgroup)
+                   ("work"     . ?w)
+                   ("studies"  . ?s)
+                   ("esols"    . ?e)
+                   ("personal" . ?p)
+                   (:endgroup))
+   ;; minimal prompt for tags
+   org-fast-tag-selection-single-key 'expert
+   ;; allow evaluating some languages with Org Babel
+   org-babel-load-languages '((emacs-lisp . t)
+                              (shell      . t)
+                              (sql        . t)
+                              (prolog     . t))
+   ;; do not ask for confirmation when evaluating Org source blocks
+   org-confirm-babel-evaluate nil
+   ;; extra modules to load along with Org
+   org-modules '(ol-bbdb
+                 ol-bibtex
+                 ol-docview
+                 ol-gnus
+                 ol-info
+                 ol-irc
+                 ol-mhe
+                 ol-rmail
+                 ol-eww
+                 ob-sql
+                 org-tempo)
+   ;; refile Org entries to my agenda file(s)
+   org-refile-targets '((org-agenda-files . (:maxlevel . 5))
+                        (nil . (:maxlevel . 3)))
+   ;; use full outline path when prompting for refile target
+   org-refile-use-outline-path t
+   ;; archive for Org entries
+   org-archive-location "~/org/journal.org::datetree/* Finished Tasks   :ARCHIVE:"
+   ;; enable italic text in code
+   modus-themes-italic-constructs t
+   ;; enable bold text in code
+   modus-themes-bold-constructs t
+   ;; use fixed-pitch text where appropriate
+   modus-themes-mixed-fonts t
+   ;; make prompts bold
+   modus-themes-prompts '(bold)
+   ;; give source blocks a distinct background shade
+   modus-themes-org-blocks 'gray-background
+   ;; use variable-pitch text here are there
+   modus-themes-variable-pitch-ui t
+   ;; fine-tune some theme settings
+   modus-themes-common-palette-overrides '((border-mode-line-active unspecified)
+                                           (border-mode-line-inactive unspecified)
+                                           (bg-mode-line-active bg-blue-intense)
+                                           (fg-mode-line-active fg-main)
+                                           (fringe unspecified)
+                                           (underline-link border)
+                                           (underline-link-visited border)
+                                           (underline-link-symbolic border)
+                                           (fg-region unspecified))
+   ;; follow links to version-controlled files without confirming
+   vc-follow-symlinks t
+   ;; jump from .c to .h files with find-sibling-file
+   find-sibling-rules '(("\\([^/]+\\)\\.c\\'" "\\1.h"))
+   ;; configure multiple SMTP accounts
+   smtpmail-multi-accounts '((esy . ("eshelshay.yaron@gmail.com"
+                                     "smtp.gmail.com" 587
+                                     "eshelshay.yaron@gmail.com"
+                                     starttls nil nil nil))
+                             (swp . ("eshel@swi-prolog.org"
+                                     "mail.swi-prolog.com" 587
+                                     "eshel@swi-prolog.org"
+                                     starttls nil nil nil))
+                             (me  . ("me@eshelyaron.com"
+                                     "mail.eshelyaron.com"
+                                     587
+                                     "me@eshelyaron.com"
+                                     starttls nil nil nil)))
+   ;; associate email addresses with SMTP accounts
+   smtpmail-multi-associations '(("[^z-a]*eshelshay\\.yaron@gmail\\.com[^z-a]*" esy)
+                                 ("[^z-a]*eshel@swi-prolog\\.org[^z-a]*"        swp)
+                                 ("[^z-a]*me@eshelyaron\\.com[^z-a]*"           me))
+   ;; set function for sending email
+   send-mail-function #'smtpmail-multi-send-it
+   message-send-mail-function #'smtpmail-multi-send-it
+   ;; set email user agent
+   mail-user-agent 'gnus-user-agent
+   ;; make Gnus not prompt as much
+   gnus-expert-user t
+   ;; make Gnus read the dribble file on startup without prompting
+   gnus-always-read-dribble-file t
+   ;; don't break pages in incoming email
+   gnus-break-pages nil
+   ;; don't show the Gnus startup screen
+   gnus-inhibit-startup-message t
+   ;; set Gnus backends
+   gnus-select-method '(nntp "news.gmane.io")
+   gnus-secondary-select-methods '((nnimap "gmail"
+                                           (nnimap-address "imap.gmail.com")
+                                           (nnimap-server-port "imaps")
+                                           (nnimap-stream ssl))
+                                   (nnimap "me"
+                                           (nnimap-address "mail.eshelyaron.com")
+                                           (nnimap-server-port "imaps")
+                                           (nnimap-stream ssl))
+                                   (nnimap "swi"
+                                           (nnimap-address "mail.swi-prolog.com")
+                                           (nnimap-server-port "imaps")
+                                           (nnimap-stream ssl)))
+   ;; simpler message
+   gnus-no-groups-message "No new articles"
+   ;; don't close other windows when opening Gnus
+   gnus-use-full-window nil
+   ;; make Gnus handle this content types
+   gnus-article-treat-types '("text/plain"
+                              "text/x-verbatim"
+                              "text/x-patch"
+                              "text/html"
+                              "text/calendar")
+   ;; capture calendar events from Gnus to my Org inbox file
+   gnus-icalendar-org-capture-file "~/org/inbox.org"
+   ;; put calendar events under the Calendar heading
+   gnus-icalendar-org-capture-headline '("Calendar")
+   ;; use ISO format for calendar dates
+   calendar-date-style 'iso
+   ;; maintain legibility of rendered text in HTML mails
+   shr-color-visible-luminance-min 75
+   ;; my Mastodon settings
+   mastodon-instance-url "https://emacs.ch"
+   mastodon-active-user "eshel"
+   ;; direct Magit to my Git checkouts directory
+   magit-repository-directories '(("~/checkouts/" . 1))
+   ;; have Dired operations target another visible Dired buffer
+   dired-dwim-target t
+   ;; use zsh in vterm by default
+   vterm-shell "/bin/zsh"
+   vterm-max-scrollback 2048
+   vterm-kill-buffer-on-exit nil
+   vterm-use-vterm-prompt-detection-method t
+   ;; use MPV with EMMS
+   emms-player-list '(emms-player-mpv)
+   bbdb-phone-style nil
+   eww-auto-rename-buffer 'title
+   browse-url-browser-function #'eww-browse-url
+   browse-url-generic-program "open"
+   ;; set up some feeds for Elfeed
+   elfeed-feeds '(
+                  ("https://snarfed.org/feed" tech)
+                  ("https://www.nirandfar.com/rss" tech blog)
+                  ("https://www.joelotter.com/posts/index.xml" indieweb)
+                  ("https://dri.es/rss.xml" indieweb)
+                  ("https://www.thisdaysportion.com/feed/index.xml" indieweb)
+                  ("http://regex.info/blog/feed" tech blog japan)
+                  ("https://eshelyaron.com/rss.xml" me)
+                  ("https://www.brautaset.org/feed.xml" blog tech)
+                  ("http://node2.feed43.com/7487052648530856.xml" comics scifi)
+                  ("https://ajroach42.com/feed.xml")
+                  ("https://writepermission.com/rss.xml" tech blog)
+                  ("https://alexschroeder.ch/wiki/feed/full/" tech)
+                  ("https://amodernist.com/all.atom" emacs)
+                  ("https://archive.casouri.cc/note/atom.xml")
+                  ("https://arcology.garden/updates.xml")
+                  ("http://www.aaronsw.com/weblog/index.xml" blog tech)
+                  ("https://atthis.link/rss.xml" tech)
+                  ("https://daily.baty.net/feed/feed.xml" blog tech)
+                  ("https://baty.net/feed" tech)
+                  ("https://betterappsec.com/feed")
+                  ("https://bitspook.in/blog/feed.xml")
+                  ("https://blog.acthompson.net/feeds/posts/default")
+                  ("https://bruda.ca/feed.php")
+                  ("https://cce.whatthefuck.computer/updates.xml")
+                  ("https://cdn.jwz.org/blog/feed/" tech)
+                  ("https://cestlaz.github.io/rss.xml" tech)
+                  ("https://changelog.complete.org/feed")
+                  ("https://daniel.haxx.se/blog/feed/" tech)
+                  ("https://drewdevault.com/blog/index.xml")
+                  ("https://emacs.dyerdwelling.family/index.xml")
+                  ("https://emacsninja.com/feed.atom" emacs)
+                  ("https://erikmcclure.com/blog/index.xml")
+                  ("https://evanhahn.com/blog/index.xml")
+                  ("https://fasterthanli.me/index.xml")
+                  ("https://feeds.buzzsprout.com/2134279.rss")
+                  ("https://flower.codes/feed.xml")
+                  ("https://garymarcus.substack.com/feed")
+                  ("https://gwern.substack.com/feed" tech blog)
+                  ("https://herman.bearblog.dev/feed/")
+                  ("https://irreal.org/blog/?feed=rss2" emacs)
+                  ("https://jcm.libsyn.com/rss/")
+                  ("https://jnd.org/feed" tech philosophy)
+                  ("https://kelar.org/~bandali/atom.xml" tech blog)
+                  ("https://kitchen-sink.kwakk.info/feed" comics)
+                  ("https://lars.ingebrigtsen.no/feed" emacs)
+                  ("https://lwn.net/headlines/rss" linux)
+                  ("https://maggieappleton.com/rss.xml" design)
+                  ("https://matklad.github.io/feed.xml")
+                  ("https://matt-rickard.com/rss" tech)
+                  ("https://njoseph.me/shaarli/feed/atom?")
+                  ("https://nullprogram.com/feed/" tech)
+                  ("https://olddeuteronomy.github.io/index.xml")
+                  ("https://parasurv.neocities.org/rss.xml" tech blog)
+                  ("https://phaazon.net/blog/feed")
+                  ("https://planet.emacslife.com/atom.xml" emacs)
+                  ("https://ploum.net/atom_en.xml")
+                  ("https://pod.fossified.com/podcast.rss" tech podcast)
+                  ("https://pouria.dev/rss.xml")
+                  ("https://project-mage.org/rss.xml")
+                  ("https://protesilaos.com/master.xml" emacs philosophy)
+                  ("https://reddit.com/r/prolog/.rss" prolog)
+                  ("https://risky.biz/rss.xml" tech)
+                  ("https://sachachua.com/blog/feed/" emacs)
+                  ("https://stephanango.com/feed.xml")
+                  ("https://stppodcast.libsyn.com/rss")
+                  ("https://takeonrules.com/index.atom")
+                  ("https://two-wrongs.com/feed")
+                  ("https://typeclasses.substack.com/feed")
+                  ("https://unixsheikh.com/feed.rss" linux)
+                  ("https://blog.gregbrockman.com/feed" tech ai)
+                  ("https://usesthis.com/feed.atom")
+                  ("https://writer13.neocities.org/rss.xml")
+                  ("https://www.cs.uni.edu/~wallingf/blog/atom.xml")
+                  ("https://www.devever.net/~hl/index.feed" tech)
+                  ("https://www.draketo.de/rss-feed.xml")
+                  ("https://www.evalapply.org/index.xml" emacs)
+                  ("https://www.fsf.org/static/fsforg/rss/news.xml" tech)
+                  ("https://www.haskellforall.com/feeds/posts/default" tech)
+                  ("https://www.murilopereira.com/index.xml")
+                  ("https://www.stilldrinking.org/rss/feed.xml")
+                  ("https://www.theregister.com/headlines.atom" tech)
+                  ("https://www.typetheoryforall.com/episodes.mp3.rss" podcast)
+                  ("https://xeiaso.net/blog.rss" tech)
+                  ("https://xenodium.com/rss.xml" emacs)
+                  ("https://xkcd.com/rss.xml" comics tech)
+                  ("http://sawv.org/rss.xml" tech blog)
+                  )
+   ;; don't use bold face for unread Elfeed entries
+   elfeed-search-face-alist '((unread (default)))
+   global-auto-revert-non-file-buffers t
+   auto-revert-verbose nil
+   query-about-changed-file t
+   kill-do-not-save-duplicates t
+   show-trailing-whitespace t
+   read-extended-command-predicate #'command-completion-default-include-p
+   completions-format 'one-column
+   completion-auto-select nil
+   completions-detailed nil
+   completion-styles '(orderless partial-completion basic)
+   completion-show-help nil
+   completions-header-format (propertize "%s candidates:\n" 'face 'shadow)
+   completion-auto-help 'visual
+   completions-max-height 16
+   completion-auto-wrap t
+   corfu-cycle t
+   corfu-indexed-start     1
+   shell-kill-buffer-on-exit t
+   ;; allow disabling confirming before compilation via local variables
+   safe-local-variable-values '((compilation-read-command . nil))
+   TeX-view-program-selection '((output-pdf "PDF Tools"))
+   TeX-source-correlate-start-server t
+   xref-show-definitions-function #'consult-xref
+   xref-show-xrefs-function       #'consult-xref
+   xref-search-program             'ripgrep
+   sqlformat-command 'pgformatter
+   sql-input-ring-file-name (expand-file-name ".sqli-history" user-emacs-directory)
+   ;; use relative line numbers
+   display-line-numbers-type 'relative
+   ;; persist Git commit message history
+   savehist-additional-variables '(log-edit-comment-ring)
+   )
+  (setq-default indent-tabs-mode nil))
+
+(esy/init-step theme
+  "Load the `modus-vivendi' theme."
+  (require-theme 'modus-themes)
+  (load-theme 'modus-vivendi))
+
+(esy/init-step fonts
+  "Set up the Iosevka font family."
+  (set-face-attribute 'default nil        :family "Iosevka" :height 130)
+  (set-face-attribute 'fixed-pitch nil    :family "Iosevka")
+  (set-face-attribute 'variable-pitch nil :family "Iosevka Etoile"))
+
+(esy/init-step load-path
+  "Add custom code directory to `load-path'."
+  (add-to-list 'load-path (expand-file-name "lisp/" user-emacs-directory))
+  (autoload 'some-button "some-button" nil t)
+  (autoload 'esy-capture "esy-capture" nil t)
+  (autoload 'pdf-view-mode "pdf-view"  nil t))
+
+(esy/init-step commands
+  "Define custom commands."
+  (defvar-keymap transpose-lines-repeat-map
+    :doc "Repeat map for \\[transpose-lines]"
+    "C-t" #'transpose-lines)
+
+  (put 'transpose-lines 'repeat-map 'transpose-lines-repeat-map)
+
+  ;; utility commands
+  (defun esy/kill-dwim ()
+    "When region is active, kill region, otherwise kill last word."
+    (interactive)
+    (if (region-active-p)
+        (let ((beg (region-beginning))
+              (end (region-end)))
+          (kill-region beg end))
+      (let ((end (point)))
+        (backward-word)
+        (kill-region (point) end))))
+
+  (defun esy/pulse-line (&optional _)
+    "Pulse current line."
+    (interactive)
+    (pulse-momentary-highlight-one-line))
+
+  (defun esy/seconds-to-date-string (seconds)
+    "Decode and display the date corresponding to SECONDS."
+    (interactive (list (if (use-region-p)
+                           (string-to-number (buffer-substring (region-beginning)
+                                                               (region-end)))
+                         (read-number "Unix timestamp: " (round (float-time))))))
+    (message (format-time-string "%FT%T%z" (seconds-to-time seconds))))
+
+  (defun esy/find-init-file (init)
+    "Find the Emacs INIT file."
+    (interactive (list user-init-file))
+    (find-file init))
+
+  (defun esy/eww-target ()
+    (require 'eww)
+    (if (use-region-p)
+        (let ((target (buffer-substring-no-properties (use-region-beginning)
+                                                      (use-region-end))))
+          (add-to-history 'eww-prompt-history target)
+          target)
+      (completing-read "Browse or search: "
+                       eww-prompt-history nil nil nil
+                       'eww-prompt-history
+                       (eww-suggested-uris))))
+
+  (defun esy/eww (target)
+    "Browse or search for TARGET."
+    (interactive (list (esy/eww-target)))
+    (eww target))
+
+  (with-eval-after-load 'shell
+    (keymap-set shell-mode-map "SPC" #'comint-magic-space))
+
+  (defvar-local esy/log-buffer-filename nil
+    "File in which the current buffer is logged.")
+
+  (defun esy/log-buffer ()
+    "Save the current buffer under the ~/logs/ directory."
+    (interactive)
+    (let ((filename (or esy/log-buffer-filename
+                        (setq esy/log-buffer-filename
+                              (expand-file-name
+                               (concat mode-name
+                                       "_"
+                                       (format-time-string
+                                        "%Y%m%d%H%M%S"
+                                        (current-time))
+                                       ".log")
+                               "~/logs")))))
+      (save-restriction
+        (widen)
+        (write-region (point-min)
+                      (point-max)
+                      filename))))
+
+  (with-eval-after-load 'comint
+    (keymap-set comint-mode-map "C-c C-q" #'esy/log-buffer)))
+
+(esy/init-step startup-message
+  "Override the default startup message."
+ (define-advice startup-echo-area-message (:override () report-init-time)
+   (format "%s started in %s.  Hack away."
+           (propertize "Emacs"           'face 'success)
+           (propertize (emacs-init-time) 'face 'error  ))))
+
+(esy/init-step packages
+  "Ensure external packages are installed."
+  (package-install-selected-packages))
+
+(esy/init-step prog-mode-hook
+  "Extend standard programming mode hooks."
+  (dolist (mode '(bug-reference-prog-mode
+                  display-fill-column-indicator-mode
+                  display-line-numbers-mode
+                  flymake-mode
+                  flyspell-prog-mode
+                  rainbow-delimiters-mode))
+    (add-hook 'prog-mode-hook mode))
+  (add-hook 'lisp-data-mode-hook #'paredit-mode))
+
+(esy/init-step other-hooks
+  "Extend other standard hooks."
+  (add-hook 'text-mode-hook #'flyspell-mode)
+  (add-hook 'completion-list-mode-hook
+            (lambda ()
+              (setq-local cursor-in-non-selected-windows nil))))
+
+(esy/init-step bindings
+  "Bind some keys."
+  (keymap-global-set "C-c w" #'esy/eww)
+  (keymap-global-set "C-c c" #'org-capture)
+  (keymap-global-set "C-c l" #'org-store-link)
+  (keymap-global-set "C-c a" #'org-agenda)
+  (keymap-global-set "C-c v" #'vterm-other-window)
+  (keymap-global-set "C-c S" #'scratch-buffer)
+  (keymap-global-set "C-c m" #'esy/emms-map)
+  (keymap-global-set "C-c !" #'consult-flymake)
+  (keymap-global-set "C-c C" #'esy-capture)
+  (keymap-global-set "C-c O" #'openai-chat)
+  (keymap-global-set "C-c E" #'elfeed)
+  (keymap-global-set "C-c G" #'gnus)
+  (keymap-global-set "C-c M" #'mastodon)
+  (keymap-global-set "C-c V" #'vertalen-at-point)
+  (keymap-global-set "C-c F" #'esy/find-init-file)
+  (keymap-global-set "C-c P" #'list-packages)
+  (keymap-global-set "C-c SPC" #'consult-mark)
+  (keymap-global-set "<remap> <kill-region>" #'esy/kill-dwim)
+  (keymap-global-set "<remap> <goto-line>" #'consult-goto-line)
+  (keymap-global-set "<remap> <suspend-frame>" #'zap-up-to-char)
+  (keymap-global-set "<remap> <imenu>" #'consult-imenu)
+  (keymap-global-set "<remap> <make-frame>" #'duplicate-line)
+  (keymap-global-set "M-#" #'define-word-at-point)
+  (keymap-global-set "M-o" #'previous-buffer)
+  (keymap-global-set "M-O" #'next-buffer)
+  (keymap-global-set "C-," #'backward-delete-char)
+  (keymap-global-set "C-." #'embark-act)
+  (keymap-global-set "C-;" #'avy-goto-char-timer)
+  (keymap-global-set "C-s-f" #'toggle-frame-fullscreen)
+  (keymap-global-set "C-s-l" #'esy/pulse-line)
+
+  (keymap-set ctl-x-map "b" #'consult-buffer)
+  (keymap-set ctl-x-4-map "b" #'consult-buffer-other-window)
+  (keymap-set ctl-x-5-map "b" #'consult-buffer-other-frame)
+  (keymap-set ctl-x-map "r b" #'consult-bookmark)
+  (keymap-set ctl-x-map "C-b" #'ibuffer)
+
+  (keymap-set search-map "r" #'rg)
+  (keymap-set search-map "l" #'rg-literal)
+  (keymap-set search-map "b" #'some-button)
+
+  ;; unbind some keys
+  (dolist (key '("s-a" "s-d" "s-e" "s-f" "s-g" "s-h" "s-j" "s-k" "s-l"
+                 "s-m" "s-o" "s-p" "s-q" "s-s" "s-t" "s-u" "s-w" "s-x"
+                 "s-y" "s-z"))
+    (keymap-global-unset key))
+
+  ;; enable some commands
+  (dolist (command '(set-goal-column
+                     narrow-to-region
+                     narrow-to-page
+                     downcase-region
+                     upcase-region))
+    (put command 'disabled nil))
+
+  ;; disable some commands
+  (put 'suspend-frame 'disabled t))
+
+(esy/init-step project
+  "Configure project management commands."
+  (with-eval-after-load 'project
+    (define-advice project--find-in-directory (:override (dir) no-remote-projects)
+      (unless (file-remote-p dir)
+        (run-hook-with-args-until-success 'project-find-functions dir)))
+    (add-to-list 'project-switch-commands '(project-compile "Compile"))
+    (add-to-list 'project-switch-commands '(rg-project "rg"))
+    (add-to-list 'project-switch-commands '(magit-project-status "Magit"))
+    (add-to-list 'project-switch-commands '(project-shell "Shell"))
+    (when (boundp 'project-prefix-map)
+      (define-key project-prefix-map "R" #'rg-project)
+      (define-key project-prefix-map "m" #'magit-project-status))))
+
+(esy/init-step sql
+  "Configure SQL connections."
+  (with-eval-after-load 'sql
+    (setq sql-connection-alist (let* ((a (auth-source-search :port 5432
+                                                             :max  6
+                                                             :require '(:user :port :secret :host)))
+                                      (d (nth 0 a))
+                                      (p (nth 1 a))
+                                      (c (nth 2 a))
+                                      (e (nth 3 a))
+                                      (f (nth 4 a))
+                                      (i (nth 5 a)))
+                                 `((dev
+                                    (sql-product 'postgres)
+                                    (sql-user ,(plist-get d :user))
+                                    (sql-port 5432)
+                                    (sql-password ,(funcall (plist-get d :secret)))
+                                    (sql-server ,(plist-get d :host))
+                                    (sql-database "alerts"))
+                                   (prod
+                                    (sql-product 'postgres)
+                                    (sql-user ,(plist-get p :user))
+                                    (sql-port 5432)
+                                    (sql-password ,(funcall (plist-get p :secret)))
+                                    (sql-server ,(plist-get p :host))
+                                    (sql-database "alerts"))
+                                   (cgs
+                                    (sql-product 'postgres)
+                                    (sql-user ,(plist-get c :user))
+                                    (sql-port 5432)
+                                    (sql-password ,(funcall (plist-get c :secret)))
+                                    (sql-server ,(plist-get c :host))
+                                    (sql-database "container_graph"))
+                                   (ten
+                                    (sql-product 'postgres)
+                                    (sql-user ,(plist-get e :user))
+                                    (sql-port 5432)
+                                    (sql-password ,(funcall (plist-get e :secret)))
+                                    (sql-server ,(cadr (split-string (plist-get e :host)  (rx "^"))))
+                                    (sql-database ,(car (split-string (plist-get e :host) (rx "^")))))
+                                   (ac
+                                    (sql-product 'postgres)
+                                    (sql-user ,(plist-get f :user))
+                                    (sql-port 5432)
+                                    (sql-password ,(funcall (plist-get f :secret)))
+                                    (sql-server ,(cadr (split-string (plist-get f :host)  (rx "^"))))
+                                    (sql-database ,(car (split-string (plist-get f :host) (rx "^")))))
+                                   (int
+                                    (sql-product 'postgres)
+                                    (sql-user ,(plist-get i :user))
+                                    (sql-port 5432)
+                                    (sql-password ,(funcall (plist-get i :secret)))
+                                    (sql-server ,(cadr (split-string (plist-get i :host)  (rx "^"))))
+                                    (sql-database ,(car (split-string (plist-get i :host) (rx "^"))))))))
+    (add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
+    (define-key sql-mode-map (kbd "C-c C-f") #'sqlformat)
+    (define-advice sql-comint-postgres (:around (fun &rest args) use-sql-password)
+      (let ((process-environment
+             (nconc
+              (list (format "PGPASSWORD=%s" sql-password))
+              process-environment)))
+        (apply fun args)))))
+
+(esy/init-step org
+  "Configure Org mode."
   (with-eval-after-load 'org
-    (add-to-list 'org-src-lang-modes '("prolog" . sweeprolog))))
-(esy/init-step 'sweep)
-
-(with-eval-after-load 'rg
-  (add-to-list 'rg-custom-type-aliases '("Prolog" . "*.pl *.plt *.pro *.prolog")))
-;; (use-package rg
-;;   :config
-;;   (add-to-list 'rg-custom-type-aliases '("Prolog" . "*.pl *.plt *.pro *.prolog")))
-(esy/init-step 'rg)
-
-(use-package terraform-mode
-  :config
-  (setq terraform--resource-name-face font-lock-function-name-face
-        terraform--resource-type-face font-lock-type-face))
-(esy/init-step 'terraform)
-
-(with-eval-after-load 'consult
-  (with-eval-after-load 'embark
-    (require 'embark-consult)))
-(esy/init-step 'embark-consult)
-
-;;;; compile this file (if changed) when Emacs is killed
-(defun esy/compile-config ()
-  (when (file-newer-than-file-p user-init-file
-                                (byte-compile-dest-file user-init-file))
-    (byte-compile-file user-init-file)))
-
-(add-hook 'kill-emacs-hook #'esy/compile-config 10)
-(esy/init-step 'kill-hook)
-
-(let ((last nil))
-  (dolist (step (reverse esy/init-step-times))
-    (message "%s (%f) %s"
-             (format-time-string "%T.%N" (cdr step))
-             (if last
-                 (float-time (time-subtract (cdr step) last))
-               0)
-             (car step))
-    (setq last (cdr step))))
+    (keymap-unset org-mode-map "C-," t)
+    (add-to-list 'org-src-lang-modes '("prolog" . sweeprolog)))
+
+  (with-eval-after-load 'org-agenda
+    (add-to-list 'org-agenda-custom-commands
+                 '("w" "Work TODOs" tags-todo "+work"))))
+
+(esy/init-step email
+  "Configure email via `gnus'."
+  (with-eval-after-load 'gnus
+    (require 'gnus-icalendar)
+    (add-hook 'gnus-group-mode-hook #'gnus-topic-mode)
+    (gnus-icalendar-setup)
+    (gnus-icalendar-org-setup)
+    (all-the-icons-gnus-setup)
+    (bbdb-initialize 'gnus 'mail 'message)))
+
+(esy/init-step dired
+  "Configure `dired'."
+  (defun esy/local-all-the-icons-dired-mode ()
+    (unless (file-remote-p default-directory)
+      (all-the-icons-dired-mode)))
+
+  (with-eval-after-load 'dired
+    (put 'dired-find-alternate-file 'disabled nil)
+    (add-hook 'dired-mode-hook #'esy/local-all-the-icons-dired-mode)))
+
+(esy/init-step tramp
+  "Configure remote access via `tramp'."
+  (with-eval-after-load 'tramp
+    (tramp-set-completion-function "ssh" '((tramp-parse-netrc "~/.authinfo.gpg")))
+    (add-to-list 'backup-directory-alist (cons tramp-file-name-regexp nil))))
+
+(esy/init-step proced
+  "Configure `proced'."
+  (with-eval-after-load 'proced
+    (add-hook 'proced-mode-hook #'proced-toggle-auto-update)))
+
+(esy/init-step time
+  "Configure `world-clock'."
+  (with-eval-after-load 'time
+    (add-to-list 'zoneinfo-style-world-list '("Europe/Amsterdam" "Amsterdam"))))
+
+(esy/init-step flyspell
+  "Configure spelling errors correction via `flyspell'."
+  (with-eval-after-load 'flyspell
+    (keymap-unset flyspell-mode-map "C-," t)
+    (keymap-unset flyspell-mode-map "C-." t)
+    (keymap-unset flyspell-mode-map "C-;" t)))
+
+(esy/init-step minibuffer-completion
+  "Configure minibuffer completions."
+  (add-to-list 'display-buffer-alist
+               '("\\*Completions\\*"
+                 (display-buffer-reuse-window display-buffer-at-bottom)
+                 (window-parameters . ((mode-line-format . none)))))
+
+  (define-advice completion-file-name-table (:filter-args (args) no-remote-file-name-completion)
+    (let ((string (car args))
+          (pred (cadr args))
+          (action (caddr args)))
+      (if (and (file-remote-p string) (eq pred #'file-exists-p))
+          (list string nil action)
+        (list string pred action))))
+
+  (define-key minibuffer-local-completion-map
+              [remap previous-line] #'minibuffer-previous-completion)
+  (define-key minibuffer-local-completion-map
+              [remap next-line]     #'minibuffer-next-completion)
+
+  (with-eval-after-load 'marginalia
+    (add-hook 'marginalia-mode-hook #'all-the-icons-completion-marginalia-setup))
+
+  (with-eval-after-load 'consult
+    (with-eval-after-load 'embark
+      (require 'embark-consult))))
+
+(esy/init-step capf
+  "Set up some global `completion-at-point-functions'."
+  (defun esy/dabbrev-capf ()
+    "Workaround for issue with `dabbrev-capf'."
+    (require 'dabbrev)
+    (dabbrev--reset-global-variables)
+    (setq dabbrev-case-fold-search nil)
+    (dabbrev-capf))
+
+  (add-to-list 'completion-at-point-functions #'esy/dabbrev-capf)
+
+  (defun esy/file-capf ()
+    "File completion at point function."
+    (let ((bs (bounds-of-thing-at-point 'filename)))
+      (when bs
+        (let* ((start (car bs))
+               (end   (cdr bs)))
+          `(,start ,end completion--file-name-table . (:exclusive no))))))
+
+  (add-to-list 'completion-at-point-functions #'esy/file-capf))
+
+(esy/init-step lin
+  "Configure highlighting of the current line via `lin'."
+  (with-eval-after-load 'lin
+    (add-to-list 'lin-mode-hooks 'gnus-summary-mode-hook)
+    (add-to-list 'lin-mode-hooks 'gnus-group-mode-hook)
+    (add-to-list 'lin-mode-hooks 'gnus-server-mode-hook)))
+
+(esy/init-step icons
+  "Configure icons via `all-the-icons'."
+  (with-eval-after-load 'all-the-icons
+    (add-to-list 'all-the-icons-extension-icon-alist
+                 '("pl" all-the-icons-alltheicon "prolog"
+                   :height 1.1 :face all-the-icons-lmaroon))))
+
+(esy/init-step corfu
+  "Configure `completion-in-region' UI with `corfu'."
+  (let ((original-completion-in-region-function completion-in-region-function))
+   (defun esy/setup-corfu (&rest args)
+     (setq completion-in-region-function original-completion-in-region-function)
+     (global-corfu-mode)
+     (apply #'completion-in-region args)))
+  (setq completion-in-region-function #'esy/setup-corfu)
+  (with-eval-after-load 'corfu
+    (defun esy/margin-formatter (metadata)
+      "Format METADATA for `corfu-margin-formatters'."
+      (pcase (cdr (assoc 'category metadata))
+        ('file (lambda (string)
+                 (concat (if (string-suffix-p "/" string)
+                             (all-the-icons-icon-for-dir string)
+                           (all-the-icons-icon-for-file string))
+                         " ")))
+        ('dabbrev (lambda (_) "… "))))
+    (add-to-list 'corfu-margin-formatters #'esy/margin-formatter)
+    (corfu-indexed-mode)))
+
+(esy/init-step minor-modes
+  "Enable some global minor modes."
+  (dolist (mode '(
+                  column-number-mode
+                  context-menu-mode
+                  display-time-mode
+                  display-battery-mode
+                  global-auto-revert-mode
+                  global-diff-hl-mode
+                  global-whitespace-cleanup-mode
+                  lin-global-mode
+                  marginalia-mode
+                  minibuffer-depth-indicate-mode
+                  mouse-avoidance-mode
+                  pixel-scroll-precision-mode
+                  recentf-mode
+                  repeat-mode
+                  save-place-mode
+                  savehist-mode
+                  show-paren-mode
+                  transient-mark-mode
+                  ))
+    (funcall mode)))
+
+(esy/init-step emms
+  "Set up EMMS."
+  (dolist (command '(emms-start
+                     emms-stop
+                     emms-next
+                     emms-previous
+                     emms-pause
+                     emms-seek
+                     emms-seek-to
+                     emms-seek-forward
+                     emms-seek-backward
+                     emms-show))
+    (autoload command "emms"))
+
+  (defvar-keymap esy/emms-map
+    :doc "My keymap for EMMS commands."
+    :prefix 'esy/emms-map
+    "N" #'emms-next
+    "P" #'emms-previous
+    "S" #'emms-stop
+    "b" #'emms-seek-backward
+    "f" #'emms-seek-forward
+    "k" #'emms-seek
+    "m" #'emms-show
+    "p" #'emms-pause
+    "s" #'emms-start
+    "t" #'emms-seek-to)
+
+  (with-eval-after-load 'emms
+    (emms-minimalistic)))
+
+(esy/init-step pdf
+  "Configure PDF handling."
+  (with-eval-after-load 'pdf-view
+    (require 'pdf-tools))
+  (with-eval-after-load 'pdf-tools
+    (pdf-tools-install :no-query)
+    (add-hook 'pdf-view-mode-hook #'pdf-view-midnight-minor-mode))
+  (add-to-list 'revert-without-query "\\.pdf\\'"))
+
+(esy/init-step tex
+  "Configure TeX via `auctex'."
+  (with-eval-after-load 'tex
+    (add-hook 'TeX-after-compilation-finished-functions
+              #'TeX-revert-document-buffer)))
+
+(esy/init-step vterm
+  "Configure terminal emulation via `vterm'."
+  (with-eval-after-load 'vterm
+    (add-to-list 'vterm-tramp-shells '("kubernetes" "/bin/bash"))))
+
+(esy/init-step paredit
+  "Remove some over-intrusive keybindings in `paredit'."
+  (with-eval-after-load 'paredit
+    (keymap-unset paredit-mode-map "M-s" t)
+    (keymap-unset paredit-mode-map "M-?" t)))
+
+(esy/init-step sweep
+  "Configure Prolog integration via `sweeprolog'."
+  (with-eval-after-load 'sweeprolog
+    (add-hook 'sweeprolog-mode-hook #'sweeprolog-electric-layout-mode)
+    (add-hook 'sweeprolog-top-level-mode-hook #'compilation-shell-minor-mode)
+    (keymap-global-set "C-c p" sweeprolog-prefix-map)))
+
+(esy/init-step rg
+  "Configure recursive grepping via `rg'."
+  (with-eval-after-load 'rg
+    (add-to-list 'rg-custom-type-aliases '("Prolog" . "*.pl *.plt *.pro *.prolog"))))
+
+(esy/init-step terraform
+  "Configure custom faces in `terraform-mode'."
+  (with-eval-after-load 'terraform-mode
+    (setq terraform--resource-name-face font-lock-function-name-face
+          terraform--resource-type-face font-lock-type-face)))
+
+(esy/init-step auto-mode
+  "Associate major modes with files based on their extensions."
+  (dolist (cell '(("Dockerfile"  . dockerfile-ts-mode)
+                  ("\\.ya?ml\\'" . yaml-ts-mode)
+                  ("\\.json\\'"  . json-ts-mode)
+                  ("\\.rb\\'"    . ruby-ts-mode)
+                  ("\\.rs\\'"    . rust-ts-mode)
+                  ("\\.go\\'"    . go-ts-mode)
+                  ("\\.pdf\\'"   . pdf-view-mode)
+                  ("\\.plt?\\'"  . sweeprolog-mode)))
+    (push cell auto-mode-alist)))
+
+(esy/init-step kill-hook
+  "Compile this file (if changed) when Emacs is killed."
+  (defun esy/compile-config ()
+    (interactive)
+    (when (file-newer-than-file-p user-init-file
+                                  (byte-compile-dest-file user-init-file))
+      (byte-compile-file user-init-file))
+    (when (file-newer-than-file-p early-init-file
+                                  (byte-compile-dest-file early-init-file))
+      (byte-compile-file early-init-file)))
+
+  (add-hook 'kill-emacs-hook #'esy/compile-config 10))
+
+(dolist (step (sort esy/init-step-times (lambda (l r) (time-less-p (cdr r) (cdr l)))))
+  (message "%f %s"
+           (float-time (cdr step))
+           (car step)))
 
 (provide 'init)
 ;;; init.el ends here
diff --git a/.emacs.d/lisp/esy-capture.el b/.emacs.d/lisp/esy-capture.el
new file mode 100644 (file)
index 0000000..3fdb739
--- /dev/null
@@ -0,0 +1,51 @@
+;;; esy-capture.el --- capture a new post            -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2023  Eshel Yaron
+
+;; Author: Eshel Yaron <me@eshelyaron.com>
+;; Keywords: convenience
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(defvar esy-capture-directory "~/checkouts/eshelyaron.com/org/posts")
+
+(defvar esy-capture-keywords '("emacs" "prolog" "language" "politics"))
+
+(defun esy-capture (title subtitle description keywords date)
+  (interactive (list (read-string "Title: ")
+                     (read-string "Subtitle: ")
+                     (read-string "Description: ")
+                     (completing-read-multiple "Keywords: " esy-capture-keywords)
+                     (format-time-string "%F")))
+  (find-file (expand-file-name
+              (concat date
+                      "-"
+                      (downcase (string-join (string-split title (rx (+ (not alnum))) t) "-"))
+                      ".org")
+              esy-capture-directory))
+  (insert "#+TITLE:       " title                      "\n"
+          "#+SUBTITLE:    " subtitle                   "\n"
+          "#+DESCRIPTION: " description                "\n"
+          "#+KEYWORDS:    " (string-join keywords " ") "\n"
+          "#+DATE:        " date                       "\n")
+  (set-buffer-modified-p nil))
+
+(provide 'esy-capture)
+;;; esy-capture.el ends here