]> git.eshelyaron.com Git - dotfiles.git/commitdiff
Remove 'esy/init-step' wrappers
authorEshel Yaron <me@eshelyaron.com>
Sun, 23 Jul 2023 17:37:31 +0000 (20:37 +0300)
committerEshel Yaron <me@eshelyaron.com>
Sun, 23 Jul 2023 17:37:31 +0000 (20:37 +0300)
.emacs.d/init.el

index f2379662fa96649e04ca73c9f265c636dfc1b336..04ce40b891b4b4f47b01a1f409f26676d7a2edad 100644 (file)
 (when (eq system-type 'darwin)
   (setq default-frame-alist '((fullscreen . fullboth))))
 
-(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
-   bug-reference-url-format "https://debbugs.gnu.org/%s"
-   ;; save bookmarks immediately
-   bookmark-save-flag 1
-   ;; show matches count in isearch prompt
-   isearch-lazy-count t
-   ;; kill Eat terminal buffers when their process exits
-   eat-kill-buffer-on-exit t
-   ;; don't spawn new frames in Ediff
-   ediff-window-setup-function #'ediff-setup-windows-plain
-   duplicate-line-final-position -1
-   tramp-kubernetes-namespace "argo"
-   ;; 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 '(
-                               avy
-                               bbdb
-                               corfu
-                               debbugs
-                               devdocs
-                               diff-hl
-                               eat
-                               eglot
-                               ef-themes
-                               elfeed
-                               embark-consult
-                               emms
-                               gnu-elpa-keyring-update
-                               gnuplot
-                               graphql-mode
-                               graphviz-dot-mode
-                               htmlize
-                               ialign
-                               keycast
-                               kubernetes
-                               lin
-                               magit
-                               markdown-mode
-                               mastodon
-                               ob-prolog
-                               orderless
-                               org
-                               org-transclusion
-                               osm
-                               package-lint
-                               paredit
-                               pdf-tools
-                               rainbow-delimiters
-                               rainbow-mode
-                               rg
-                               smtpmail-multi
-                               sqlformat
-                               sweeprolog
-                               terraform-mode
-                               vterm
-                               whitespace-cleanup-mode
-                               )
-   ;; configure Org capture templates
-   org-capture-templates '(("t" "Todo [inbox]" entry
-                            (file+headline "~/org/inbox.org" "Tasks")
-                            "** TODO [#B] %^{Task}    %^g
+;;; 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))))
+
+;;; 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
+ bug-reference-url-format "https://debbugs.gnu.org/%s"
+ ;; save bookmarks immediately
+ bookmark-save-flag 1
+ ;; show matches count in isearch prompt
+ isearch-lazy-count t
+ ;; kill Eat terminal buffers when their process exits
+ eat-kill-buffer-on-exit t
+ ;; don't spawn new frames in Ediff
+ ediff-window-setup-function #'ediff-setup-windows-plain
+ duplicate-line-final-position -1
+ tramp-kubernetes-namespace "argo"
+ ;; 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 '(
+                             avy
+                             bbdb
+                             corfu
+                             debbugs
+                             devdocs
+                             diff-hl
+                             eat
+                             eglot
+                             ef-themes
+                             elfeed
+                             embark-consult
+                             emms
+                             gnu-elpa-keyring-update
+                             gnuplot
+                             graphql-mode
+                             graphviz-dot-mode
+                             htmlize
+                             ialign
+                             keycast
+                             kubernetes
+                             lin
+                             magit
+                             markdown-mode
+                             mastodon
+                             ob-prolog
+                             orderless
+                             org
+                             org-transclusion
+                             osm
+                             package-lint
+                             paredit
+                             pdf-tools
+                             rainbow-delimiters
+                             rainbow-mode
+                             rg
+                             smtpmail-multi
+                             sqlformat
+                             sweeprolog
+                             terraform-mode
+                             vterm
+                             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)
-                   ("adi"      . ?a)
-                   ("holland"  . ?h)
-                   ("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-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:"
-   ;; associate Org Babel languages with major modes
-   org-src-lang-modes '(("SWI-Prolog" . sweeprolog)
-                        ("Dockerfile" . dockerfile-ts)
-                        ("bash"       . sh)
-                        ("shell"      . sh)
-                        ("toml"       . toml-ts))
-   ;; preview LaTeX fragments in Org buffers via SVG
-   org-preview-latex-default-process 'dvisvgm
-   ;; don't auto-save remote files
-   remote-file-name-inhibit-auto-save t
-   ;; don't ask me about it either
-   tramp-allow-unsafe-temporary-files t
-   ;; increase maximum number of recent files Emacs remembers
-   recentf-max-saved-items 128
-   ;; increase maximum kill ring size
-   kill-ring-max 256
-   ;; save text copied from another program to the kill ring
-   save-interprogram-paste-before-kill 2048
-   ;; have C-u followed by repeated C-SPC keep popping
-   set-mark-command-repeat-pop t
-   ;; 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))
-   ef-themes-mixed-fonts t
-   ef-themes-variable-pitch-ui t
-   ef-themes-region nil
-   osm-copyright nil
-   ;; 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))
-                             ;; (uva  . ("eshel.yaron@student.uva.nl"
-                             ;;         "smtp.office365.com"
-                             ;;         587
-                             ;;         "eshel.yaron@student.uva.nl"
-                             ;;         starttls nil nil nil))
-                             )
-   ;; associate email addresses with SMTP accounts
-   smtpmail-multi-associations '(("eshelshay\.yaron@gmail\.com"   esy)
-                                 ("eshel@swi-prolog\.org"         swp)
-                                 ("me@eshelyaron\.com"             me)
-                                 ;; ("eshel\.yaron@student\.uva\.nl" uva)
+                          :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)
+                 ("adi"      . ?a)
+                 ("holland"  . ?h)
+                 ("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-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:"
+ ;; associate Org Babel languages with major modes
+ org-src-lang-modes '(("SWI-Prolog" . sweeprolog)
+                      ("Dockerfile" . dockerfile-ts)
+                      ("bash"       . sh)
+                      ("shell"      . sh)
+                      ("toml"       . toml-ts))
+ ;; preview LaTeX fragments in Org buffers via SVG
+ org-preview-latex-default-process 'dvisvgm
+ ;; don't auto-save remote files
+ remote-file-name-inhibit-auto-save t
+ ;; don't ask me about it either
+ tramp-allow-unsafe-temporary-files t
+ ;; increase maximum number of recent files Emacs remembers
+ recentf-max-saved-items 128
+ ;; increase maximum kill ring size
+ kill-ring-max 256
+ ;; save text copied from another program to the kill ring
+ save-interprogram-paste-before-kill 2048
+ ;; have C-u followed by repeated C-SPC keep popping
+ set-mark-command-repeat-pop t
+ ;; 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))
+ ef-themes-mixed-fonts t
+ ef-themes-variable-pitch-ui t
+ ef-themes-region nil
+ osm-copyright nil
+ ;; 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))
+                           ;; (uva  . ("eshel.yaron@student.uva.nl"
+                           ;;         "smtp.office365.com"
+                           ;;         587
+                           ;;         "eshel.yaron@student.uva.nl"
+                           ;;         starttls nil nil nil))
+                           )
+ ;; associate email addresses with SMTP accounts
+ smtpmail-multi-associations '(("eshelshay\.yaron@gmail\.com"   esy)
+                               ("eshel@swi-prolog\.org"         swp)
+                               ("me@eshelyaron\.com"             me)
+                               ;; ("eshel\.yaron@student\.uva\.nl" uva)
+                               )
+ ;; 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
+ read-mail-command 'gnus
+ ;; 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-cite-parse-max-size nil
+ gnus-face-1 'bold
+ gnus-face-2 'gnus-cite-1
+ gnus-face-3 'shadow
+ gnus-summary-line-format "%1{%U%R%z%}%B%2{%~(form (replace-regexp-in-string \" via .*\" \"\" (gnus-summary-from-or-to-or-newsgroups gnus-tmp-header gnus-tmp-from)))@%} %3{(%&user-date;, %k)%}:%* %s\n"
+ gnus-simplify-subject-functions '(gnus-simplify-subject-re)
+ gnus-treat-display-smileys nil
+ 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))
+                                 ;; (nnimap "uva"
+                                 ;;         (nnimap-address "outlook.office365.com")
+                                 ;;         (nnimap-server-port "imaps")
+                                 ;;         (nnimap-stream ssl))
                                  )
-   ;; 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
-   read-mail-command 'gnus
-   ;; 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-cite-parse-max-size nil
-   gnus-face-1 'bold
-   gnus-face-2 'gnus-cite-1
-   gnus-face-3 'shadow
-   gnus-summary-line-format "%1{%U%R%z%}%B%2{%~(form (replace-regexp-in-string \" via .*\" \"\" (gnus-summary-from-or-to-or-newsgroups gnus-tmp-header gnus-tmp-from)))@%} %3{(%&user-date;, %k)%}:%* %s\n"
-   gnus-simplify-subject-functions '(gnus-simplify-subject-re)
-   gnus-treat-display-smileys nil
-   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))
-                                   ;; (nnimap "uva"
-                                   ;;         (nnimap-address "outlook.office365.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
-   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)
-   ;; free-style numbering plan
-   bbdb-phone-style nil
-   ;; allow cycling through mail address completion candidate
-   bbdb-complete-mail-allow-cycling t
-   ;; don't pop up BBDB records after completing mail addresses
-   bbdb-completion-display-record nil
-   eww-auto-rename-buffer 'title
-   browse-url-browser-function #'eww-browse-url
-   browse-url-generic-program "open"
-   global-auto-revert-non-file-buffers t
-   auto-revert-verbose nil
-   query-about-changed-file t
-   ;; show flymake diagnostics as overlays at eol
-   ;; flymake-show-diagnostics-at-end-of-line 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
-   completion-styles '(orderless partial-completion basic)
-   completion-show-help nil
-   ;; completions-header-format nil
-   ;; completion-auto-help 'visible
-   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
-   ;; include CWD in shell command prompts
-   shell-command-prompt-show-cwd t
-   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)
-   ;; IRC stuff
-   rcirc-default-nick "esy"
-   rcirc-server-alist '(("irc.libera.chat"
-                         :channels ("#emacs")
-                         :port 6697
-                         :encryption tls))
-   rcirc-log-flag t
-   ;; use my custom project-prompting function
-   project-prompter #'esy/read-project-by-name
-   ;; have common bindings initially hidden in the output of C-h b
-   describe-bindings-outline-rules `((match-regexp
-                                      .
-                                      ,(regexp-opt
-                                        '("Key translations"
-                                          "Global Bindings:"
-                                          "Function key map translations"
-                                          "pixel-scroll-precision-mode"
-                                          "context-menu-mode")))))
-  (setq-default indent-tabs-mode nil))
-
-(esy/init-step theme
-  "Load and enable a custom theme."
-  ;; (require-theme 'modus-themes)
-  ;; (load-theme 'modus-vivendi)
-  (require 'ef-themes)
-
-  (defvar esy/ef-themes-ring (make-ring 16))
-
-  (defun esy/ef-themes-load-previous ()
-    (interactive)
-    (when (< 0 (ring-length esy/ef-themes-ring))
-      (let ((theme (ring-remove esy/ef-themes-ring 0)))
-        (message "Loaded `%s'" theme)
-        (ef-themes--load-theme theme))))
-
-  (defun esy/ef-themes-load-random ()
-    (interactive)
-    (let* ((theme (ef-themes--current-theme))
-           (themes (seq-filter
-                    (lambda (other-theme)
-                      (not (equal other-theme theme)))
-                    '(
-                      ef-autumn
-                      ef-bio
-                      ef-cherie
-                      ef-cyprus
-                      ef-dark
-                      ef-day
-                      ef-duo-dark
-                      ef-duo-light
-                      ef-elea-dark
-                      ef-elea-light
-                      ef-frost
-                      ef-kassio
-                      ef-light
-                      ef-maris-dark
-                      ef-maris-light
-                      ef-night
-                      ef-spring
-                      ef-summer
-                      ef-symbiosis
-                      ef-trio-dark
-                      ef-trio-light
-                      ef-winter
-                      )))
-           (n (random (length themes)))
-           (pick (nth n themes))
-           (loaded (if (null pick) (car themes) pick)))
-      (ring-insert esy/ef-themes-ring theme)
-      (ef-themes--load-theme loaded)
-      (message "Loaded `%s'" loaded)))
-
-  (esy/ef-themes-load-random))
-
-(esy/init-step fonts
-  "Set up the Iosevka font family."
-  (when (eq system-type 'darwin)
-    (set-face-attribute 'default nil :height 130))
-  (set-face-attribute 'default nil        :family "Iosevka")
-  (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))))
-
-  (defvar duplicate-line-final-position)
-
-  (defun duplicate-line-stay (arg)
-    (interactive "p")
-    (let ((duplicate-line-final-position 0))
-      (duplicate-line arg)))
-
-  (defun esy/ttyper ()
-    (interactive)
-    (eat "ttyper" t))
-
-  (defun esy/hut-builds ()
-    (interactive)
-    (let ((eat-kill-buffer-on-exit nil))
-      (eat "hut builds show -f" t)))
-
-  (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))
-
-  (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))
-
-  (defun esy/recent-log-summary ()
-    "Display a summary of my website's most recent access log."
-    (interactive)
-    (async-shell-command (string-join (list "ssh"
-                                            "root@direct.eshelyaron.com"
-                                            (shell-quote-argument
-                                             (string-join '("grep -E '\"GET.+\" 2' /var/log/apache2/access.log"
-                                                            "tr -d '\"'"
-                                                            "tr -d \"'\""
-                                                            "cut -f 7,11,12 -d ' '"
-                                                            "sort"
-                                                            "uniq -c"
-                                                            "sort -n")
-                                                          " | ")))
-                                      " ")
-                         "*Recent Log Summary*"))
-
-  (defun esy/access-log-summary ()
-    "Display a summary of my website's access log."
-    (interactive)
-    (let ((default-directory "/ssh:root@direct.eshelyaron.com:/var/log/apache2/"))
-      (async-shell-command (string-join  '("zcat access.log.*.gz"
-                                           "cat - access.log access.log.1"
-                                           "grep -E '\"GET.+\" 2'"
-                                           "tr -d '\"'"
-                                           "tr -d \"'\""
-                                           "cut -f 7,11,12 -d ' '"
-                                           "sort"
-                                           "uniq -c"
-                                           "sort -n")
-                                         " | ")
-                           "*Access Log Summary*")))
-
-  (defun esy/clone (remote)
-    (interactive (list (let ((default (thing-at-point 'url)))
-                         (read-string (format-prompt "Clone" default)
-                                      nil nil default))))
-    (let ((dir (expand-file-name
-                (file-name-base (car (url-path-and-query
-                                      (url-generic-parse-url remote))))
-                "~/checkouts")))
-      (vc-clone remote 'Git dir)
-      (find-file dir)))
-
-  (defun esy/json-path-to-position (pos)
-    "Return the JSON path from the document's root to the element at POS.
+ ;; 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
+ 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)
+ ;; free-style numbering plan
+ bbdb-phone-style nil
+ ;; allow cycling through mail address completion candidate
+ bbdb-complete-mail-allow-cycling t
+ ;; don't pop up BBDB records after completing mail addresses
+ bbdb-completion-display-record nil
+ eww-auto-rename-buffer 'title
+ browse-url-browser-function #'eww-browse-url
+ browse-url-generic-program "open"
+ global-auto-revert-non-file-buffers t
+ auto-revert-verbose nil
+ query-about-changed-file t
+ ;; show flymake diagnostics as overlays at eol
+ ;; flymake-show-diagnostics-at-end-of-line 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
+ completion-styles '(orderless partial-completion basic)
+ completion-show-help nil
+ ;; completions-header-format nil
+ ;; completion-auto-help 'visible
+ 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
+ ;; include CWD in shell command prompts
+ shell-command-prompt-show-cwd t
+ 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)
+ ;; IRC stuff
+ rcirc-default-nick "esy"
+ rcirc-server-alist '(("irc.libera.chat"
+                       :channels ("#emacs")
+                       :port 6697
+                       :encryption tls))
+ rcirc-log-flag t
+ ;; use my custom project-prompting function
+ project-prompter #'esy/read-project-by-name
+ ;; have common bindings initially hidden in the output of C-h b
+ describe-bindings-outline-rules `((match-regexp
+                                    .
+                                    ,(regexp-opt
+                                      '("Key translations"
+                                        "Global Bindings:"
+                                        "Function key map translations"
+                                        "pixel-scroll-precision-mode"
+                                        "context-menu-mode")))))
+(setq-default indent-tabs-mode nil)
+
+;;; Load and enable a custom theme
+;; (require-theme 'modus-themes)
+;; (load-theme 'modus-vivendi)
+(require 'ef-themes)
+
+(defvar esy/ef-themes-ring (make-ring 16))
+
+(defun esy/ef-themes-load-previous ()
+  (interactive)
+  (when (< 0 (ring-length esy/ef-themes-ring))
+    (let ((theme (ring-remove esy/ef-themes-ring 0)))
+      (message "Loaded `%s'" theme)
+      (ef-themes--load-theme theme))))
+
+(defun esy/ef-themes-load-random ()
+  (interactive)
+  (let* ((theme (ef-themes--current-theme))
+         (themes (seq-filter
+                  (lambda (other-theme)
+                    (not (equal other-theme theme)))
+                  '(
+                    ef-autumn
+                    ef-bio
+                    ef-cherie
+                    ef-cyprus
+                    ef-dark
+                    ef-day
+                    ef-duo-dark
+                    ef-duo-light
+                    ef-elea-dark
+                    ef-elea-light
+                    ef-frost
+                    ef-kassio
+                    ef-light
+                    ef-maris-dark
+                    ef-maris-light
+                    ef-night
+                    ef-spring
+                    ef-summer
+                    ef-symbiosis
+                    ef-trio-dark
+                    ef-trio-light
+                    ef-winter
+                    )))
+         (n (random (length themes)))
+         (pick (nth n themes))
+         (loaded (if (null pick) (car themes) pick)))
+    (ring-insert esy/ef-themes-ring theme)
+    (ef-themes--load-theme loaded)
+    (message "Loaded `%s'" loaded)))
+
+(esy/ef-themes-load-random)
+
+;;; Set up the Iosevka font family
+(when (eq system-type 'darwin)
+  (set-face-attribute 'default nil :height 130))
+(set-face-attribute 'default nil        :family "Iosevka")
+(set-face-attribute 'fixed-pitch nil    :family "Iosevka")
+(set-face-attribute 'variable-pitch nil :family "Iosevka Etoile")
+
+;;; 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)
+
+;;; 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))))
+
+(defvar duplicate-line-final-position)
+
+(defun duplicate-line-stay (arg)
+  (interactive "p")
+  (let ((duplicate-line-final-position 0))
+    (duplicate-line arg)))
+
+(defun esy/ttyper ()
+  (interactive)
+  (eat "ttyper" t))
+
+(defun esy/hut-builds ()
+  (interactive)
+  (let ((eat-kill-buffer-on-exit nil))
+    (eat "hut builds show -f" t)))
+
+(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))
+
+(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))
+
+(defun esy/recent-log-summary ()
+  "Display a summary of my website's most recent access log."
+  (interactive)
+  (async-shell-command (string-join (list "ssh"
+                                          "root@direct.eshelyaron.com"
+                                          (shell-quote-argument
+                                           (string-join '("grep -E '\"GET.+\" 2' /var/log/apache2/access.log"
+                                                          "tr -d '\"'"
+                                                          "tr -d \"'\""
+                                                          "cut -f 7,11,12 -d ' '"
+                                                          "sort"
+                                                          "uniq -c"
+                                                          "sort -n")
+                                                        " | ")))
+                                    " ")
+                       "*Recent Log Summary*"))
+
+(defun esy/access-log-summary ()
+  "Display a summary of my website's access log."
+  (interactive)
+  (let ((default-directory "/ssh:root@direct.eshelyaron.com:/var/log/apache2/"))
+    (async-shell-command (string-join  '("zcat access.log.*.gz"
+                                         "cat - access.log access.log.1"
+                                         "grep -E '\"GET.+\" 2'"
+                                         "tr -d '\"'"
+                                         "tr -d \"'\""
+                                         "cut -f 7,11,12 -d ' '"
+                                         "sort"
+                                         "uniq -c"
+                                         "sort -n")
+                                       " | ")
+                         "*Access Log Summary*")))
+
+(defun esy/clone (remote)
+  (interactive (list (let ((default (thing-at-point 'url)))
+                       (read-string (format-prompt "Clone" default)
+                                    nil nil default))))
+  (let ((dir (expand-file-name
+              (file-name-base (car (url-path-and-query
+                                    (url-generic-parse-url remote))))
+              "~/checkouts")))
+    (vc-clone remote 'Git dir)
+    (find-file dir)))
+
+(defun esy/json-path-to-position (pos)
+  "Return the JSON path from the document's root to the element at POS.
 
 The path is represented as a list of strings and integers,
 corresponding to the object keys and array indices that lead from
 the root to the element at POS."
-    (named-let loop ((node (treesit-node-at pos)) (acc nil))
-      (if-let ((parent (treesit-parent-until
-                        node
-                        (lambda (n)
-                          (member (treesit-node-type n)
-                                  '("pair" "array"))))))
-          (loop parent
-                (cons
-                 (pcase (treesit-node-type parent)
-                   ("pair"
-                    (treesit-node-text
-                     (treesit-node-child (treesit-node-child parent 0) 1) t))
-                   ("array"
-                    (named-let check ((i 1))
-                      (if (< pos (treesit-node-end (treesit-node-child parent i)))
-                          (/ (1- i) 2)
-                        (check (+ i 2))))))
-                 acc))
-        acc)))
-
-  (defun esy/json-path-at-point (point &optional kill)
-    "Display the JSON path at POINT.  When KILL is non-nil, kill it too.
+  (named-let loop ((node (treesit-node-at pos)) (acc nil))
+    (if-let ((parent (treesit-parent-until
+                      node
+                      (lambda (n)
+                        (member (treesit-node-type n)
+                                '("pair" "array"))))))
+        (loop parent
+              (cons
+               (pcase (treesit-node-type parent)
+                 ("pair"
+                  (treesit-node-text
+                   (treesit-node-child (treesit-node-child parent 0) 1) t))
+                 ("array"
+                  (named-let check ((i 1))
+                    (if (< pos (treesit-node-end (treesit-node-child parent i)))
+                        (/ (1- i) 2)
+                      (check (+ i 2))))))
+               acc))
+      acc)))
+
+(defun esy/json-path-at-point (point &optional kill)
+  "Display the JSON path at POINT.  When KILL is non-nil, kill it too.
 
 Interactively, POINT is point and KILL is the prefix argument."
-    (interactive "d\nP" json-ts-mode)
-    (let ((path (mapconcat (lambda (o) (format "%s" o))
-                           (esy/json-path-to-position point)
-                           ".")))
-      (if kill
-          (progn (kill-new path) (message "Copied: %s" path))
-        (message path))
-      path))
-
-  (defun esy/transcribe ()
-    (interactive)
-    (message "Recording...")
-    (let ((process
-           (start-process "ffmpeg" nil "ffmpeg"
-                          "-f" "avfoundation"
-                          "-i" "1:0"
-                          "-ar" "16000"
-                          "-y"
-                          "/tmp/foo.wav")))
-      (set-transient-map
-       (make-sparse-keymap) nil
-       (lambda ()
-         (message "Stopping recording")
-         (interrupt-process process)
-         (accept-process-output process 1 nil t)
-         (message "Transcribing...")
-         (message
-          (string-trim-right
-           (string-trim-left
-            (with-temp-buffer
-              (call-process "whisper"
-                            nil '(t nil) nil
-                            "-m"
-                            "/Users/eshelyaron/checkouts/whisper.cpp/models/ggml-base.en.bin"
-                            "--no-timestamps" "-f" "/tmp/foo.wav")
-              (goto-char (point-min))
-              (while (search-forward "[BLANK_AUDIO]" nil t)
-                (replace-match "" nil t))
-              (buffer-string))))))
-       "Recording...  Press any key to stop")))
-
-  (defun esy/record (timeout)
-    (interactive "p")
-    (message "Recording...")
-    (call-process "ffmpeg"
-                  nil nil nil
-                  "-f" "avfoundation"
-                  "-i" "1:0" "-t"
-                  (number-to-string (max timeout 2))
-                  "-ar" "16000" "-y" "/tmp/foo.wav")
-    (openai-chat (string-trim-right
-                  (string-trim-left
-                   (with-temp-buffer
-                     (call-process "whisper"
-                                   nil '(t nil) nil
-                                   "-m"
-                                   "/Users/eshelyaron/checkouts/whisper.cpp/models/ggml-base.en.bin"
-                                   "--no-timestamps" "-f" "/tmp/foo.wav")
-                     (goto-char (point-min))
-                     (while (search-forward "[BLANK_AUDIO]" nil t)
-                       (replace-match "" nil t))
-                     (buffer-string))))))
-
-  (defun esy/dedicate-window (window flag)
-    (interactive (list (get-buffer-window) (not current-prefix-arg)))
-    (message "Window is %s dedicated to buffer %s."
-             (if flag (if (window-dedicated-p) "already" "now") "no longer")
-             (buffer-name))
-    (set-window-dedicated-p window flag))
-
-  (defun esy/bump (tag-prefix)
-    (interactive (list "v"))
-    (require 'lisp-mnt)
-    (require 'magit-apply)
-    (let ((date (format-time-string "%F" (current-time)))
-          (current-version (save-excursion (lm-header "package-version"))))
-      (unless current-version
-        (user-error "No Package-Version Elisp header found"))
-      (let ((next-version (mapconcat #'number-to-string
-                                     (pcase (version-to-list
-                                             current-version)
-                                       (`(,major ,minor ,patch)
-                                        (list major (1+ minor) patch)))
-                                     ".")))
-        (with-current-buffer (find-file "NEWS.org")
-          (goto-char (point-min))
-          (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t)
-          (beginning-of-line)
-          (if (looking-at (rx "* Version "
-                              (group-n 1
-                                (+ digit) "."
-                                (+ digit) "."
-                                (+ digit))
-                              " in development"))
-              (progn
-                (setq next-version (match-string-no-properties 1))
-                (end-of-line)
-                (delete-char -14)
-                (insert "on " date)
-                (forward-char 2))
-            (insert "* Version " next-version " on " date "\n\n")))
-        (lm-header "package-version")
-        (delete-region (point) (pos-eol))
-        (insert next-version)
-        (vc-print-root-log)
-        (find-file-other-window "NEWS.org")
-        (named-let loop ()
-          (recursive-edit)
-          (unless (y-or-n-p "OK to stage, commit and tag the new version?")
-            (loop)))
-        (unless (= (call-process "git" nil nil nil
-                                 "add" (buffer-file-name) "NEWS.org")
-                   0)
-          (error "Git add failed"))
-        (unless (= (call-process "git" nil nil nil
-                                 "commit" "-m"
-                                 (concat "Announce recent changes "
-                                         "in NEWS.org "
-                                         "and bump version to "
-                                         next-version))
-                   0)
-          (error "Git commit failed"))
-        (unless (= (call-process "git" nil nil nil
-                                 "tag" "-s" "-m"
-                                 (concat "Release version "
-                                         next-version)
-                                 (concat tag-prefix next-version))
-                   0)
-          (error "Git tag failed"))))))
-
-(esy/init-step auto-exec-permissions
-  "Ensure scripts ran with `executable-interpret' are executable."
-  (with-eval-after-load 'executable
-    (define-advice executable-interpret (:before (&rest _) ensure-executable)
-      (unless (file-exists-p buffer-file-name)
-        (basic-save-buffer))
-      (executable-make-buffer-file-executable-if-script-p))))
-
-(esy/init-step pulse-on-window-selection-change
-  "Pulse the line around point after switching windows."
-
-  (defun esy/pulse-on-window-selection-change (&rest _)
-    (pulse-momentary-highlight-one-line))
-
-  (add-hook 'window-selection-change-functions
-            #'esy/pulse-on-window-selection-change))
-
-(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))
-
-(esy/init-step bindings
-  "Bind some keys."
-  (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-publish-create-post)
-  (keymap-global-set "C-c O" #'openai-chat)
-  (keymap-global-set "C-c E" #'elfeed)
-  (keymap-global-set "C-c T" #'esy/ttyper)
-  (keymap-global-set "C-c R" #'esy/record)
-  (keymap-global-set "C-c G" #'gnus)
-  (keymap-global-set "C-c M" #'mastodon)
-  (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 "C-c 1" #'delete-other-windows)
-  (keymap-global-set "C-c 2" #'split-window-below)
-  (keymap-global-set "C-c 3" #'split-window-right)
-  (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 "s-n" #'duplicate-line)
-  (keymap-global-set "s-p" #'duplicate-line-stay)
-  (keymap-global-set "s-u" #'universal-argument)
-  (keymap-global-set "s--" #'negative-argument)
-  (keymap-global-set "M-#" #'dictionary-search)
-  (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-global-set "\"" #'insert-pair)
-  (keymap-global-set "<f5>" #'esy/ef-themes-load-random)
-  (keymap-global-set "s-]"  #'esy/ef-themes-load-random)
-  (keymap-global-set "s-["  #'esy/ef-themes-load-previous)
-
-  (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)
-
-  (keymap-set window-prefix-map "p" #'windmove-swap-states-up)
-  (keymap-set window-prefix-map "n" #'windmove-swap-states-down)
-  (keymap-set window-prefix-map "a" #'windmove-swap-states-left)
-  (keymap-set window-prefix-map "e" #'windmove-swap-states-right)
-  (keymap-set window-prefix-map "d" #'esy/dedicate-window)
-
-  (dolist (command '(windmove-swap-states-up
-                     windmove-swap-states-down
-                     windmove-swap-states-left
-                     windmove-swap-states-right))
-    (put command 'repeat-map 'window-prefix-map))
-
-  ;; digit arguments with the super modifier
-  (dotimes (i 10)
-    (keymap-global-set (concat "s-" (number-to-string i))
-                       #'digit-argument))
-
-  ;; 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-q" "s-s" "s-t" "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))
-
-    (defvar esy/project-name-history nil)
-
-    (defvar esy/projects-directory "~/checkouts/")
-
-    (defun esy/read-project-by-name ()
-      "Read a project name and return its root directory.
+  (interactive "d\nP" json-ts-mode)
+  (let ((path (mapconcat (lambda (o) (format "%s" o))
+                         (esy/json-path-to-position point)
+                         ".")))
+    (if kill
+        (progn (kill-new path) (message "Copied: %s" path))
+      (message path))
+    path))
+
+(defun esy/transcribe ()
+  (interactive)
+  (message "Recording...")
+  (let ((process
+         (start-process "ffmpeg" nil "ffmpeg"
+                        "-f" "avfoundation"
+                        "-i" "1:0"
+                        "-ar" "16000"
+                        "-y"
+                        "/tmp/foo.wav")))
+    (set-transient-map
+     (make-sparse-keymap) nil
+     (lambda ()
+       (message "Stopping recording")
+       (interrupt-process process)
+       (accept-process-output process 1 nil t)
+       (message "Transcribing...")
+       (message
+        (string-trim-right
+         (string-trim-left
+          (with-temp-buffer
+            (call-process "whisper"
+                          nil '(t nil) nil
+                          "-m"
+                          "/Users/eshelyaron/checkouts/whisper.cpp/models/ggml-base.en.bin"
+                          "--no-timestamps" "-f" "/tmp/foo.wav")
+            (goto-char (point-min))
+            (while (search-forward "[BLANK_AUDIO]" nil t)
+              (replace-match "" nil t))
+            (buffer-string))))))
+     "Recording...  Press any key to stop")))
+
+(defun esy/record (timeout)
+  (interactive "p")
+  (message "Recording...")
+  (call-process "ffmpeg"
+                nil nil nil
+                "-f" "avfoundation"
+                "-i" "1:0" "-t"
+                (number-to-string (max timeout 2))
+                "-ar" "16000" "-y" "/tmp/foo.wav")
+  (openai-chat (string-trim-right
+                (string-trim-left
+                 (with-temp-buffer
+                   (call-process "whisper"
+                                 nil '(t nil) nil
+                                 "-m"
+                                 "/Users/eshelyaron/checkouts/whisper.cpp/models/ggml-base.en.bin"
+                                 "--no-timestamps" "-f" "/tmp/foo.wav")
+                   (goto-char (point-min))
+                   (while (search-forward "[BLANK_AUDIO]" nil t)
+                     (replace-match "" nil t))
+                   (buffer-string))))))
+
+(defun esy/dedicate-window (window flag)
+  (interactive (list (get-buffer-window) (not current-prefix-arg)))
+  (message "Window is %s dedicated to buffer %s."
+           (if flag (if (window-dedicated-p) "already" "now") "no longer")
+           (buffer-name))
+  (set-window-dedicated-p window flag))
+
+(defun esy/bump (tag-prefix)
+  (interactive (list "v"))
+  (require 'lisp-mnt)
+  (require 'magit-apply)
+  (let ((date (format-time-string "%F" (current-time)))
+        (current-version (save-excursion (lm-header "package-version"))))
+    (unless current-version
+      (user-error "No Package-Version Elisp header found"))
+    (let ((next-version (mapconcat #'number-to-string
+                                   (pcase (version-to-list
+                                           current-version)
+                                     (`(,major ,minor ,patch)
+                                      (list major (1+ minor) patch)))
+                                   ".")))
+      (with-current-buffer (find-file "NEWS.org")
+        (goto-char (point-min))
+        (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t)
+        (beginning-of-line)
+        (if (looking-at (rx "* Version "
+                            (group-n 1
+                              (+ digit) "."
+                              (+ digit) "."
+                              (+ digit))
+                            " in development"))
+            (progn
+              (setq next-version (match-string-no-properties 1))
+              (end-of-line)
+              (delete-char -14)
+              (insert "on " date)
+              (forward-char 2))
+          (insert "* Version " next-version " on " date "\n\n")))
+      (lm-header "package-version")
+      (delete-region (point) (pos-eol))
+      (insert next-version)
+      (vc-print-root-log)
+      (find-file-other-window "NEWS.org")
+      (named-let loop ()
+        (recursive-edit)
+        (unless (y-or-n-p "OK to stage, commit and tag the new version?")
+          (loop)))
+      (unless (= (call-process "git" nil nil nil
+                               "add" (buffer-file-name) "NEWS.org")
+                 0)
+        (error "Git add failed"))
+      (unless (= (call-process "git" nil nil nil
+                               "commit" "-m"
+                               (concat "Announce recent changes "
+                                       "in NEWS.org "
+                                       "and bump version to "
+                                       next-version))
+                 0)
+        (error "Git commit failed"))
+      (unless (= (call-process "git" nil nil nil
+                               "tag" "-s" "-m"
+                               (concat "Release version "
+                                       next-version)
+                               (concat tag-prefix next-version))
+                 0)
+        (error "Git tag failed")))))
+
+;;; Ensure scripts ran with `executable-interpret' are executable
+(with-eval-after-load 'executable
+  (define-advice executable-interpret (:before (&rest _) ensure-executable)
+    (unless (file-exists-p buffer-file-name)
+      (basic-save-buffer))
+    (executable-make-buffer-file-executable-if-script-p)))
+
+;;; Pulse the line around point after switching windows
+
+(defun esy/pulse-on-window-selection-change (&rest _)
+  (pulse-momentary-highlight-one-line))
+
+(add-hook 'window-selection-change-functions
+          #'esy/pulse-on-window-selection-change)
+
+;;; 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  )))
+
+;;; Ensure external packages are installed
+(package-install-selected-packages)
+
+;;; 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)
+
+;;; Extend other standard hooks
+(add-hook 'text-mode-hook #'flyspell-mode)
+
+;;; Bind some keys
+(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-publish-create-post)
+(keymap-global-set "C-c O" #'openai-chat)
+(keymap-global-set "C-c E" #'elfeed)
+(keymap-global-set "C-c T" #'esy/ttyper)
+(keymap-global-set "C-c R" #'esy/record)
+(keymap-global-set "C-c G" #'gnus)
+(keymap-global-set "C-c M" #'mastodon)
+(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 "C-c 1" #'delete-other-windows)
+(keymap-global-set "C-c 2" #'split-window-below)
+(keymap-global-set "C-c 3" #'split-window-right)
+(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 "s-n" #'duplicate-line)
+(keymap-global-set "s-p" #'duplicate-line-stay)
+(keymap-global-set "s-u" #'universal-argument)
+(keymap-global-set "s--" #'negative-argument)
+(keymap-global-set "M-#" #'dictionary-search)
+(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-global-set "\"" #'insert-pair)
+(keymap-global-set "<f5>" #'esy/ef-themes-load-random)
+(keymap-global-set "s-]"  #'esy/ef-themes-load-random)
+(keymap-global-set "s-["  #'esy/ef-themes-load-previous)
+
+(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)
+
+(keymap-set window-prefix-map "p" #'windmove-swap-states-up)
+(keymap-set window-prefix-map "n" #'windmove-swap-states-down)
+(keymap-set window-prefix-map "a" #'windmove-swap-states-left)
+(keymap-set window-prefix-map "e" #'windmove-swap-states-right)
+(keymap-set window-prefix-map "d" #'esy/dedicate-window)
+
+(dolist (command '(windmove-swap-states-up
+                   windmove-swap-states-down
+                   windmove-swap-states-left
+                   windmove-swap-states-right))
+  (put command 'repeat-map 'window-prefix-map))
+
+;; digit arguments with the super modifier
+(dotimes (i 10)
+  (keymap-global-set (concat "s-" (number-to-string i))
+                     #'digit-argument))
+
+;; 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-q" "s-s" "s-t" "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)
+
+;;; 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))
+
+  (defvar esy/project-name-history nil)
+
+  (defvar esy/projects-directory "~/checkouts/")
+
+  (defun esy/read-project-by-name ()
+    "Read a project name and return its root directory.
 
 If no known project matches the selected name, prompt for a
 sub-directory of `esy/projects-directory' using the selected name
 as the initial input for completion, and return that directory."
-      (let* ((name-dir-alist
-              (delete
-               nil
-               (mapcar (lambda (dir)
-                         (when-let ((proj (project-current nil dir)))
-                           (cons (project-name proj) dir)))
-                       (project-known-project-roots))))
-             (current (project-current))
-             (default (and current (project-name current)))
-             (name (completing-read (format-prompt "Project" default)
-                                    name-dir-alist
-                                    nil nil nil
-                                    'esy/project-name-history
-                                    default)))
-        (or (alist-get name name-dir-alist nil nil #'string=)
-            (let* ((dir (read-directory-name "Project root directory: "
-                                             esy/projects-directory
-                                             nil t name))
-                   (project (project-current nil dir)))
-              (when project (project-remember-project project))
-              dir))))))
-
-(esy/init-step sql
-  "Configure SQL connections."
-  (with-eval-after-load 'sql
-    (defun esy/update-sql-connection-alist ()
-      (interactive)
-      (auth-source-forget-all-cached)
-      (setq sql-connection-alist (delete nil
-                                         (mapcar (lambda (source)
-                                                   (pcase (split-string (plist-get source :host)
-                                                                        (rx "^"))
-                                                     (`(,con ,db ,host)
-                                                      (list (intern con)
-                                                            (list 'sql-product ''postgres)
-                                                            (list 'sql-user  (plist-get source :user))
-                                                            (list 'sql-port  (string-to-number (plist-get source :port)))
-                                                            (list 'sql-password  (funcall (plist-get source :secret)))
-                                                            (list 'sql-server host)
-                                                            (list 'sql-database db)))))
-                                                 (auth-source-search :port 5432 :max 10)))))
-    (esy/update-sql-connection-alist)
-    (add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
-    (add-hook 'sql-interactive-mode-hook #'abbrev-mode)
-    (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
-    (keymap-unset org-mode-map "C-," t)
-    (esy-publish-setup))
-
-  (with-eval-after-load 'org-agenda
-    (add-to-list 'org-agenda-custom-commands
-                 '("w" "Work TODOs" tags-todo "+work"))
-    (add-to-list 'org-agenda-custom-commands
-                 '("h" "Holland TODOs" tags-todo "+holland"))))
-
-(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)
-    (bbdb-initialize 'gnus 'mail 'message)))
-
-(esy/init-step dired
-  "Configure `dired'."
-  (with-eval-after-load 'dired
-    (put 'dired-find-alternate-file 'disabled nil)))
-
-(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))
-    (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))))))
-
-(esy/init-step proced
-  "Configure `proced'."
-  (with-eval-after-load 'proced
-    (add-hook 'proced-mode-hook (lambda ()
-                                  (setq proced-auto-update-flag t)))))
-
-(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)
-    (keymap-unset flyspell-mode-map "C-M-i" t)))
-
-(esy/init-step minibuffer-completion
-  "Configure minibuffer completions."
-
-  (defvar esy/completing-read-commands nil)
-
-  (add-to-list 'savehist-additional-variables
-               'esy/completing-read-commands)
-
-  (define-advice completing-read (:before (&rest _) record-command)
-    (cl-incf (alist-get this-command esy/completing-read-commands 0)))
-
-  (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)))
-
-  (dolist (key-binding '(("C-p" . minibuffer-previous-completion)
-                         ("C-n" . minibuffer-next-completion)
-                         ("M-j" . minibuffer-force-complete-and-exit)))
-    (keymap-set minibuffer-local-completion-map
-                (car key-binding)
-                (cdr key-binding)))
-
-  (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)
-    (pcase (let ((inhibit-message t))
-             (ignore-errors (dabbrev-capf)))
-      (`(,beg ,end ,table . ,_)
-       (list beg end table :exclusive 'no))))
-
-  (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 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))
-        ('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
-                  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-fofrward
-    "k" #'emms-seek
-    "m" #'emms-show
-    "p" #'emms-pause
-    "s" #'emms-start
-    "t" #'emms-seek-to)
-
-  (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))
-    (put command 'repeat-map 'esy/emms-map))
-
-  (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
-    (when (eq system-type 'darwin)
-      (setq vterm-shell "/bin/zsh"))
-    (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'."
-  (setq-default prolog-system 'swi)
-  (add-to-list 'major-mode-remap-alist '(prolog-mode . sweeprolog-mode))
-  (with-eval-after-load 'sweeprolog
-    (setq sweeprolog-top-level-persistent-history
-          (locate-user-emacs-file ".sweep_history"))
-    (keymap-global-set "C-c p" sweeprolog-prefix-map)
-    (add-hook 'sweeprolog-mode-hook #'sweeprolog-electric-layout-mode)
-    (add-hook 'sweeprolog-top-level-mode-hook #'compilation-shell-minor-mode)))
-
-(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)
-                  ("\\.toml\\'"  . toml-ts-mode)
-                  ("\\.json\\'"  . json-ts-mode)
-                  ("\\.tfstate\\'"  . json-ts-mode)
-                  ("\\.ts\\'"    . typescript-ts-mode)
-                  ("\\.rb\\'"    . ruby-ts-mode)
-                  ("\\.rs\\'"    . rust-ts-mode)
-                  ("\\.go\\'"    . go-ts-mode)
-                  ("\\.pdf\\'"   . pdf-view-mode)
-                  ("\\.plt?\\'"  . prolog-mode)))
-    (push cell auto-mode-alist)))
-
-(esy/init-step kill-hook
-  "Compile this file (if changed) when Emacs is killed."
-  (defun esy/compile-config ()
+    (let* ((name-dir-alist
+            (delete
+             nil
+             (mapcar (lambda (dir)
+                       (when-let ((proj (project-current nil dir)))
+                         (cons (project-name proj) dir)))
+                     (project-known-project-roots))))
+           (current (project-current))
+           (default (and current (project-name current)))
+           (name (completing-read (format-prompt "Project" default)
+                                  name-dir-alist
+                                  nil nil nil
+                                  'esy/project-name-history
+                                  default)))
+      (or (alist-get name name-dir-alist nil nil #'string=)
+          (let* ((dir (read-directory-name "Project root directory: "
+                                           esy/projects-directory
+                                           nil t name))
+                 (project (project-current nil dir)))
+            (when project (project-remember-project project))
+            dir)))))
+
+;;; Configure SQL connections
+(with-eval-after-load 'sql
+  (defun esy/update-sql-connection-alist ()
     (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))
-
-(esy/init-step help
-  "Configure Help."
-  (with-eval-after-load 'help-fns
-    (with-eval-after-load 'shortdoc
-      (add-hook 'help-fns-describe-function-functions
-                #'shortdoc-help-fns-examples-function))))
-
-(esy/init-step dict
-  "Configure dictionary."
-  (with-eval-after-load 'dictionary
-    (setopt dictionary-search-interface   'help
-            dictionary-default-dictionary "gcide"
-            dictionary-default-strategy   "prefix"
-            dictionary-server             "dict.org")))
-
-(esy/init-step elfeed
-  "Configure news feeds."
-  (with-eval-after-load 'elfeed
-    (keymap-set elfeed-show-mode-map "S-SPC" #'scroll-down-command)
-
-    (defvar esy/feeds-file (locate-user-emacs-file "feeds.eld"))
-
-    (defun esy/feeds ()
+    (auth-source-forget-all-cached)
+    (setq sql-connection-alist (delete nil
+                                       (mapcar (lambda (source)
+                                                 (pcase (split-string (plist-get source :host)
+                                                                      (rx "^"))
+                                                   (`(,con ,db ,host)
+                                                    (list (intern con)
+                                                          (list 'sql-product ''postgres)
+                                                          (list 'sql-user  (plist-get source :user))
+                                                          (list 'sql-port  (string-to-number (plist-get source :port)))
+                                                          (list 'sql-password  (funcall (plist-get source :secret)))
+                                                          (list 'sql-server host)
+                                                          (list 'sql-database db)))))
+                                               (auth-source-search :port 5432 :max 10)))))
+  (esy/update-sql-connection-alist)
+  (add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
+  (add-hook 'sql-interactive-mode-hook #'abbrev-mode)
+  (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))))
+
+;;; Configure Org mode
+(with-eval-after-load 'org
+  (keymap-unset org-mode-map "C-," t)
+  (esy-publish-setup))
+
+(with-eval-after-load 'org-agenda
+  (add-to-list 'org-agenda-custom-commands
+               '("w" "Work TODOs" tags-todo "+work"))
+  (add-to-list 'org-agenda-custom-commands
+               '("h" "Holland TODOs" tags-todo "+holland")))
+
+;;; 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)
+  (bbdb-initialize 'gnus 'mail 'message))
+
+;;; Configure `dired'
+(with-eval-after-load 'dired
+  (put 'dired-find-alternate-file 'disabled nil))
+
+;;; 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))
+  (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)))))
+
+;;; Configure `proced'
+(with-eval-after-load 'proced
+  (add-hook 'proced-mode-hook (lambda ()
+                                (setq proced-auto-update-flag t))))
+
+;;; Configure `world-clock'
+(with-eval-after-load 'time
+  (add-to-list 'zoneinfo-style-world-list '("Europe/Amsterdam" "Amsterdam")))
+
+;;; 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)
+  (keymap-unset flyspell-mode-map "C-M-i" t))
+
+;;; Configure minibuffer completions
+
+(defvar esy/completing-read-commands nil)
+
+(add-to-list 'savehist-additional-variables
+             'esy/completing-read-commands)
+
+(define-advice completing-read (:before (&rest _) record-command)
+  (cl-incf (alist-get this-command esy/completing-read-commands 0)))
+
+(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)))
+
+(dolist (key-binding '(("C-p" . minibuffer-previous-completion)
+                       ("C-n" . minibuffer-next-completion)
+                       ("M-j" . minibuffer-force-complete-and-exit)))
+  (keymap-set minibuffer-local-completion-map
+              (car key-binding)
+              (cdr key-binding)))
+
+(with-eval-after-load 'consult
+  (with-eval-after-load 'embark
+    (require 'embark-consult)))
+
+;;; 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)
+  (pcase (let ((inhibit-message t))
+           (ignore-errors (dabbrev-capf)))
+    (`(,beg ,end ,table . ,_)
+     (list beg end table :exclusive 'no))))
+
+(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)
+
+;;; 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))
+
+;;; 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))
+      ('dabbrev (lambda (_) "… "))))
+  (add-to-list 'corfu-margin-formatters #'esy/margin-formatter)
+  (corfu-indexed-mode))
+
+;;; 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
+                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))
+
+;;; 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-fofrward
+  "k" #'emms-seek
+  "m" #'emms-show
+  "p" #'emms-pause
+  "s" #'emms-start
+  "t" #'emms-seek-to)
+
+(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))
+  (put command 'repeat-map 'esy/emms-map))
+
+(with-eval-after-load 'emms
+  (emms-minimalistic))
+
+;;; 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\\'")
+
+;;; Configure terminal emulation via `vterm'
+(with-eval-after-load 'vterm
+  (when (eq system-type 'darwin)
+    (setq vterm-shell "/bin/zsh"))
+  (add-to-list 'vterm-tramp-shells '("kubernetes" "/bin/bash")))
+
+;;; 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))
+
+;;; Configure Prolog integration via `sweeprolog'
+(setq-default prolog-system 'swi)
+(add-to-list 'major-mode-remap-alist '(prolog-mode . sweeprolog-mode))
+(with-eval-after-load 'sweeprolog
+  (setq sweeprolog-top-level-persistent-history
+        (locate-user-emacs-file ".sweep_history"))
+  (keymap-global-set "C-c p" sweeprolog-prefix-map)
+  (add-hook 'sweeprolog-mode-hook #'sweeprolog-electric-layout-mode)
+  (add-hook 'sweeprolog-top-level-mode-hook #'compilation-shell-minor-mode))
+
+;;; Configure recursive grepping via `rg'
+(with-eval-after-load 'rg
+  (add-to-list 'rg-custom-type-aliases '("Prolog" . "*.pl *.plt *.pro *.prolog")))
+
+;;; 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))
+
+;;; Associate major modes with files based on their extensions
+(dolist (cell '(("Dockerfile"  . dockerfile-ts-mode)
+                ("\\.ya?ml\\'" . yaml-ts-mode)
+                ("\\.toml\\'"  . toml-ts-mode)
+                ("\\.json\\'"  . json-ts-mode)
+                ("\\.tfstate\\'"  . json-ts-mode)
+                ("\\.ts\\'"    . typescript-ts-mode)
+                ("\\.rb\\'"    . ruby-ts-mode)
+                ("\\.rs\\'"    . rust-ts-mode)
+                ("\\.go\\'"    . go-ts-mode)
+                ("\\.pdf\\'"   . pdf-view-mode)
+                ("\\.plt?\\'"  . prolog-mode)))
+  (push cell auto-mode-alist))
+
+;;; Configure Help
+(with-eval-after-load 'help-fns
+  (with-eval-after-load 'shortdoc
+    (add-hook 'help-fns-describe-function-functions
+              #'shortdoc-help-fns-examples-function)))
+
+;;; Configure dictionary
+(with-eval-after-load 'dictionary
+  (setopt dictionary-search-interface   'help
+          dictionary-default-dictionary "gcide"
+          dictionary-default-strategy   "prefix"
+          dictionary-server             "dict.org"))
+
+;;; Configure news feeds
+(with-eval-after-load 'elfeed
+  (keymap-set elfeed-show-mode-map "S-SPC" #'scroll-down-command)
+
+  (defvar esy/feeds-file (locate-user-emacs-file "feeds.eld"))
+
+  (defun esy/feeds ()
+    (with-temp-buffer
+      (insert-file-contents-literally esy/feeds-file)
+      (goto-char (point-min))
+      (read (current-buffer))))
+
+  (defun esy/read-feed-keywords ()
+    (mapcar #'intern
+            (completing-read-multiple
+             "Keywords: "
+             (let ((keywords nil))
+               (dolist
+                   (feed-keywords
+                    (mapcar
+                     #'cdr
+                     (esy/feeds)))
+                 (mapc (lambda (keyword)
+                         (add-to-list 'keywords keyword))
+                       feed-keywords))
+               (mapcar #'symbol-name keywords)))))
+
+  (defun esy/add-feed (url keywords)
+    (interactive (let ((default (thing-at-point-url-at-point)))
+                   (list (read-string (format-prompt "Feed URL"
+                                                     default)
+                                      nil nil default)
+                         (esy/read-feed-keywords))))
+    (let ((feeds (cons (cons url keywords) (esy/feeds))))
       (with-temp-buffer
-        (insert-file-contents-literally esy/feeds-file)
-        (goto-char (point-min))
-        (read (current-buffer))))
-
-    (defun esy/read-feed-keywords ()
-      (mapcar #'intern
-              (completing-read-multiple
-               "Keywords: "
-               (let ((keywords nil))
-                 (dolist
-                     (feed-keywords
-                      (mapcar
-                       #'cdr
-                       (esy/feeds)))
-                   (mapc (lambda (keyword)
-                           (add-to-list 'keywords keyword))
-                         feed-keywords))
-                 (mapcar #'symbol-name keywords)))))
-
-    (defun esy/add-feed (url keywords)
-      (interactive (let ((default (thing-at-point-url-at-point)))
-                     (list (read-string (format-prompt "Feed URL"
-                                                       default)
-                                        nil nil default)
-                           (esy/read-feed-keywords))))
-      (let ((feeds (cons (cons url keywords) (esy/feeds))))
-        (with-temp-buffer
-          (pp (cons (cons url keywords) (esy/feeds))
-              (current-buffer))
-          (write-region (point-min)
-                        (point-max)
-                        esy/feeds-file))
-        (setq elfeed-feeds feeds)))
-
-    (defun esy/update-feeds ()
-      (interactive)
-      (setq elfeed-feeds (esy/feeds)))
-
-    (with-eval-after-load 'eww
-      (defun esy/eww-add-feed (url keywords)
-        (interactive (list (eww-read-alternate-url)
-                           (esy/read-feed-keywords))
-                     eww-mode)
-        (esy/add-feed url keywords)))
-
-    (setq
-     ;; don't use bold face for unread Elfeed entries
-     elfeed-search-face-alist '((unread (default)))
-     ;; read feeds list from a separate file
-     elfeed-feeds (esy/feeds))))
-
-(esy/init-step currency
-  "Track currency exchange rates."
-
-  (defvar esy/eur-to-ils-rates nil)
-
-  (add-to-list 'savehist-additional-variables
-               'esy/eur-to-ils-rates)
-
-  (defun esy/update-eur-to-ils-rate ()
-    (require 'dom)
-    (let* ((time (float-time))
-           (from "EUR")
-           (to "ILS")
-           (amount 1)
-           (url (url-parse-make-urlobj "https"
-                                       nil
-                                       nil
-                                       "www.x-rates.com"
-                                       nil
-                                       (concat "/calculator/?"
-                                               (url-build-query-string `(("from" ,from)
-                                                                         ("to" ,to)
-                                                                         ("amount" ,amount)))))))
-      (url-retrieve url
-                    (lambda (_)
-                      (goto-char (point-min))
-                      (search-forward "\n\n")
-                      (push (cons time
-                                  (string-to-number (caddar (dom-by-class (libxml-parse-html-region (point))
-                                                                          "ccOutputRslt"))))
-                            esy/eur-to-ils-rates))
-                    nil t)))
-
-  (run-at-time t 300 #'esy/update-eur-to-ils-rate)
-
-  (defun esy/plot-eur-to-ils-rates (height width)
-    (require 'svg)
-    (let* ((svg (svg-create width height
-                            :stroke "green"))
-           (time (float-time))
-           (frame (* 60 60 24))
-           (data (named-let loop ((rates esy/eur-to-ils-rates)
-                                  (acc nil)
-                                  (min most-positive-fixnum)
-                                  (max most-negative-fixnum))
-                   (if rates
-                       (let ((x (* (/ (+ frame (- (caar rates) time))
-                                      frame)
-                                   width)))
-                         (if (< 0 x)
-                             (let ((val (cdar rates)))
-                               (loop (cdr rates)
-                                     (cons (cons x (cdar rates)) acc)
-                                     (min val min)
-                                     (max val max)))
-                           (list acc min max)))
-                     (list acc min max))))
-           (points (nth 0 data))
-           (min (nth 1 data))
-           (max (nth 2 data))
-           (normalized-points (mapcar (lambda (xy)
-                                        (cons (car xy)
-                                              (- height (* height
-                                                           (/ (- (cdr xy) min)
-                                                              (- max min))))))
-                                      points)))
-      (svg-polyline svg normalized-points :fill-color "none")
-      (svg-image svg)))
-
-  (defun esy/plot-eur-rates ()
+        (pp (cons (cons url keywords) (esy/feeds))
+            (current-buffer))
+        (write-region (point-min)
+                      (point-max)
+                      esy/feeds-file))
+      (setq elfeed-feeds feeds)))
+
+  (defun esy/update-feeds ()
     (interactive)
-    (with-current-buffer-window "EUR to ILS" nil nil
-      (insert-image (esy/plot-eur-to-ils-rates 300 500))
-      (insert "\n")))
+    (setq elfeed-feeds (esy/feeds)))
 
-  (push '(:eval (concat "1€=" (number-to-string (cdar esy/eur-to-ils-rates)) "₪ "))
-        global-mode-string))
+  (with-eval-after-load 'eww
+    (defun esy/eww-add-feed (url keywords)
+      (interactive (list (eww-read-alternate-url)
+                         (esy/read-feed-keywords))
+                   eww-mode)
+      (esy/add-feed url keywords)))
 
-(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)))
+  (setq
+   ;; don't use bold face for unread Elfeed entries
+   elfeed-search-face-alist '((unread (default)))
+   ;; read feeds list from a separate file
+   elfeed-feeds (esy/feeds)))
+
+;;; Track currency exchange rates
+
+(defvar esy/eur-to-ils-rates nil)
+
+(add-to-list 'savehist-additional-variables
+             'esy/eur-to-ils-rates)
+
+(defun esy/update-eur-to-ils-rate ()
+  (require 'dom)
+  (let* ((time (float-time))
+         (from "EUR")
+         (to "ILS")
+         (amount 1)
+         (url (url-parse-make-urlobj "https"
+                                     nil
+                                     nil
+                                     "www.x-rates.com"
+                                     nil
+                                     (concat "/calculator/?"
+                                             (url-build-query-string `(("from" ,from)
+                                                                       ("to" ,to)
+                                                                       ("amount" ,amount)))))))
+    (url-retrieve url
+                  (lambda (_)
+                    (goto-char (point-min))
+                    (search-forward "\n\n")
+                    (push (cons time
+                                (string-to-number (caddar (dom-by-class (libxml-parse-html-region (point))
+                                                                        "ccOutputRslt"))))
+                          esy/eur-to-ils-rates))
+                  nil t)))
+
+(run-at-time t 300 #'esy/update-eur-to-ils-rate)
+
+(defun esy/plot-eur-to-ils-rates (height width)
+  (require 'svg)
+  (let* ((svg (svg-create width height
+                          :stroke "green"))
+         (time (float-time))
+         (frame (* 60 60 24))
+         (data (named-let loop ((rates esy/eur-to-ils-rates)
+                                (acc nil)
+                                (min most-positive-fixnum)
+                                (max most-negative-fixnum))
+                 (if rates
+                     (let ((x (* (/ (+ frame (- (caar rates) time))
+                                    frame)
+                                 width)))
+                       (if (< 0 x)
+                           (let ((val (cdar rates)))
+                             (loop (cdr rates)
+                                   (cons (cons x (cdar rates)) acc)
+                                   (min val min)
+                                   (max val max)))
+                         (list acc min max)))
+                   (list acc min max))))
+         (points (nth 0 data))
+         (min (nth 1 data))
+         (max (nth 2 data))
+         (normalized-points (mapcar (lambda (xy)
+                                      (cons (car xy)
+                                            (- height (* height
+                                                         (/ (- (cdr xy) min)
+                                                            (- max min))))))
+                                    points)))
+    (svg-polyline svg normalized-points :fill-color "none")
+    (svg-image svg)))
+
+(defun esy/plot-eur-rates ()
+  (interactive)
+  (with-current-buffer-window "EUR to ILS" nil nil
+    (insert-image (esy/plot-eur-to-ils-rates 300 500))
+    (insert "\n")))
+
+(push '(:eval (concat "1€=" (number-to-string (cdar esy/eur-to-ils-rates)) "₪ "))
+      global-mode-string)
 
 (provide 'init)
 ;;; init.el ends here