From b7295426c95bf968288bb6ead2db416a71ec4d3e Mon Sep 17 00:00:00 2001
From: Case Duckworth
Date: Sun, 9 Jan 2022 20:52:07 -0600
Subject: Weekend, babee

---
 init.el              | 138 ++++++++++++++++++++++++++++++++++-----------------
 lisp/+apheleia.el    |   3 +-
 lisp/+casing.el      |  25 +++++-----
 lisp/+circe.el       | 130 ++++++++++++++++++++++++++++++++++++++----------
 lisp/+elfeed.el      |   7 ---
 lisp/+embark.el      |   4 +-
 lisp/+lookup.el      |  26 ----------
 lisp/+org-capture.el |  16 ++++++
 lisp/+vertico.el     |   8 +++
 lisp/acdw.el         |  10 ++--
 10 files changed, 243 insertions(+), 124 deletions(-)
 delete mode 100644 lisp/+lookup.el

diff --git a/init.el b/init.el
index b32dfce..1a39f3c 100644
--- a/init.el
+++ b/init.el
@@ -38,11 +38,12 @@
            "C-x o" (lambda () (interactive) (switch-to-buffer nil))
            "C-x C-o" #'+open-paragraph
            "C-w" #'+kill-word-backward-or-region
-           "C-x C-1" #'delete-other-windows
-           "C-x 2" #'+split-window-below-then
-           "C-x C-2" #'+split-window-below-then
-           "C-x 3" #'+split-window-right-then
-           "C-x C-3" #'+split-window-right-then)
+           ;; "C-x C-1" #'delete-other-windows
+           ;; "C-x 2" #'+split-window-below-then
+           ;; "C-x C-2" #'+split-window-below-then
+           ;; "C-x 3" #'+split-window-right-then
+           ;; "C-x C-3" #'+split-window-right-then
+           )
   ;; C-h deletes backward - see https://idiomdrottning.org/bad-emacs-defaults
   (global-set-key (kbd "C-h") 'delete-backward-char)
   (keyboard-translate ?\C-h ?\C-?)
@@ -58,9 +59,6 @@
   (:bind "C-c s" #'+init-sort-then-save)
   (:hook #'+init-add-setup-to-imenu))
 
-(setup (:require +lookup)
-  (+lookup-mode +1))
-
 (setup (:require auth-source)
   (:option auth-sources (list (private/ "authinfo"))))
 
@@ -109,6 +107,9 @@
   (:option pulse-flag nil
            pulse-delay 0.5
            pulse-iterations 1)
+  (dolist (command '(+ace-window-or-switch-buffer
+                     pop-mark pop-globl-mark))
+    (add-to-list '+pulse-location-commands command))
   (+ensure-after-init #'+pulse-location-mode))
 
 (setup (:require reading)
@@ -137,6 +138,15 @@
   (:option
    browse-url-browser-function #'eww-browse-url
    +browse-url-browser-function browse-url-browser-function
+   browse-url-generic-program (seq-some #'executable-find
+                                        '("firefox"
+                                          "chromium"
+                                          "chrome"))
+   browse-url-generic-args (seq-some (lambda (e)
+                                       (when (equal (executable-find (car e))
+                                                    browse-url-generic-program)
+                                         (cdr e)))
+                                     '(("firefox" "--new-tab")))
    browse-url-secondary-browser-function (if (executable-find "firefox")
                                              #'browse-url-firefox
                                            #'browse-url-default-browser)
@@ -230,7 +240,7 @@
                               copy move hardlink symlink
                               shell touch)
            dired-dwim-target t)
-  (:local-set truncate-lines nil)
+  (:local-set truncate-lines t)
   (:bind "<backspace>" #'dired-up-directory
          "TAB" #'dired-subtree-cycle
          "i" #'dired-subtree-toggle
@@ -475,13 +485,19 @@
   (with-eval-after-load 'org
     (org-clock-persistence-insinuate)
     (org-link-set-parameters "tel" :follow #'+org-tel-open))
-  ;; Fancy list bullets
+  ;; Extra keywords
   (font-lock-add-keywords
    'org-mode
-   '(("^ *\\([-]\\) "
+   '(;; Fancy list bullets
+     ("^ *\\([-]\\) "
       (0 (compose-region (match-beginning 1) (match-end 1) "•")))
      ("^ *\\([+]\\) "
-      (0 (compose-region (match-beginning 1) (match-end 1) "◦"))))))
+      (0 (compose-region (match-beginning 1) (match-end 1) "◦"))))
+   (with-eval-after-load 'form-feed
+     ;; Horizontal lines
+     (font-lock-add-keywords
+      'org-mode
+      '(("^-----+" 0 'form-feed--font-lock-face t))))))
 
 (setup org-agenda
   (:option org-agenda-skip-deadline-if-done t
@@ -586,9 +602,6 @@
          (executable-find "diff"))
   (:require apheleia
             +apheleia)
-  (setf (alist-get 'indent-region apheleia-formatters) #'+apheleia-indent-region
-        (alist-get 'emacs-lisp-mode apheleia-mode-alist) 'indent-region
-        (alist-get 'lisp-interaction-mode apheleia-mode-alist) 'indent-region)
   (apheleia-global-mode +1))
 
 (setup (:straight avy)
@@ -721,9 +734,15 @@
   (advice-add #'circe-command-GQUIT :after #'+circe-gquit@kill-buffer)
 
   (:with-mode circe-chat-mode
+    (:local-set lui-input-function #'+lui-filter)
     (:hook #'enable-circe-color-nicks
            #'enable-circe-new-day-notifier
-           #'+circe-chat@set-prompt)
+           #'+circe-chat@set-prompt
+           ;; Filters
+           #'+circe-F/C-mode
+           ;; For some reason `+circe-shorten-url-mode' won't work right out of
+           ;; the gate.
+           (lambda () (run-with-idle-timer 0.25 nil #'+circe-shorten-url-mode)))
     (:bind "C-c C-s" #'circe-command-SLAP))
 
   (:with-mode lui-mode
@@ -889,8 +908,7 @@ See also `crux-reopen-as-root-mode'."
   (:option dictionary-use-single-buffer t)
   (autoload 'dictionary-search "dictionary"
     "Ask for a word and search it in all dictionaries" t)
-  (:hook #'reading-mode)
-  (define-key +lookup-mode-map "d" #'dictionary-search))
+  (:hook #'reading-mode))
 
 (setup (:straight (discord
                    :host github
@@ -918,12 +936,15 @@ See also `crux-reopen-as-root-mode'."
    elfeed-search-title-min-width 24
    elfeed-search-title-max-width 78
    elfeed-show-unique-buffers t
-   elfeed-db-directory (elfeed/ "db/" t))
-  (:+key "C-x w" #'+elfeed)
+   elfeed-db-directory (elfeed/ "db/" t)
+   elfeed-log 'debug                   ; until I can figure out syncing...
+   )
+  (:+key "C-x w" #'elfeed)
   (:with-mode elfeed-search-mode
     (:hook #'hl-line-mode)
     ;; https://old.reddit.com/r/emacs/comments/rlli0u/whats_your_favorite_defadvice/hphfh4e/
-    (advice-add #'elfeed-search-update--force :after #'elfeed-db-save))
+    (advice-add #'elfeed-search-update--force :after #'elfeed-db-save)
+    (advice-add #'elfeed :before #'elfeed-db-load))
   (:with-mode elfeed-show-mode
     (:bind "SPC" #'+elfeed-scroll-up-command
            "S-SPC" #'+elfeed-scroll-down-command)
@@ -945,14 +966,16 @@ See also `crux-reopen-as-root-mode'."
 (setup (:straight elpher))
 
 (setup (:straight embark)
-  (:also-load +embark)
+  (:require embark
+            +embark)
   (:option prefix-help-command 'embark-prefix-help-command
            embark-keymap-prompter-key ";")
   (:+key "C-." #'embark-act
          "M-." #'embark-dwim
          "<f1> B" #'embark-bindings)
   (:with-map minibuffer-local-map
-    (:bind "C-." #'embark-act))
+    (:bind "C-." #'embark-act
+           "M-." #'embark-dwim))
   (:with-map embark-file-map
     (:bind "l" #'vlf)))
 
@@ -1059,14 +1082,14 @@ See also `crux-reopen-as-root-mode'."
   (:file-match (rx ".rkt" eos)
                (rx ".scm" eos)))
 
-(setup (:straight god-mode)
-  (setq god-mode-enable-function-key-translation nil)
-  (:require god-mode
-            +god-mode)
-  (:+key "C-M-g" #'god-mode-all)
-  (:with-mode god-local-mode
-    (:bind "i" #'+god-mode-insert
-           "a" nil)))
+;; (setup (:straight god-mode)
+;;   (setq god-mode-enable-function-key-translation nil)
+;;   (:require god-mode
+;;             +god-mode)
+;;   (:+key "C-M-g" #'god-mode-all)
+;;   (:with-mode god-local-mode
+;;     (:bind "i" #'+god-mode-insert
+;;            "a" nil)))
 
 (setup (:straight helpful)
   (run-with-idle-timer 0.5 nil
@@ -1132,17 +1155,9 @@ See also `crux-reopen-as-root-mode'."
 
 (setup (:straight (lin :host gitlab :repo "protesilaos/lin"))
   (require 'lin)
-  (:hook-into dired-mode
-              elfeed-search-mode
-              git-rebase-mode
-              ibuffer-mode
-              ledger-report-mode
-              log-view-mode
-              magit-log-mode
-              notmuch-search-mode
-              notmuch-tree-mode
-              org-agenda-mode
-              tabulated-list-mode))
+  (dolist (hook lin-foreign-hooks)
+    (add-hook hook #'hl-line-mode)
+    (add-hook hook #'lin-mode)))
 
 (setup (:straight link-hint)
   (:require +link-hint)
@@ -1155,6 +1170,11 @@ See also `crux-reopen-as-root-mode'."
            "m" #'link-hint-open-multiple-links
            "M-c" #'link-hint-copy-link "c" #'link-hint-copy-link)))
 
+(setup (:straight macrostep)
+  (:require macrostep)
+  (:with-mode emacs-lisp-mode
+    (:bind "C-c e" #'macrostep-expand)))
+
 (setup (:straight marginalia)
   (marginalia-mode +1))
 
@@ -1306,8 +1326,10 @@ See also `crux-reopen-as-root-mode'."
                    :host github
                    :repo "duckwork/titlecase.el"
                    :files ("*")))
-  (:with-map +casing-mode-map
-    (:bind "t" #'titlecase-dwim)))
+  (:require titlecase)
+  (:with-map +casing-map
+    (:bind "t" #'titlecase-dwim
+           "M-t" #'titlecase-dwim)))
 
 (setup (:straight topsy)
   (:hook-into ;;prog-mode
@@ -1366,11 +1388,35 @@ See also `crux-reopen-as-root-mode'."
     (add-to-list 'native-comp-deferred-compilation-deny-list "vertico"))
   (vertico-mode +1)
   ;; Extensions
-  (:also-load vertico-directory)
+  (:also-load vertico-directory
+              vertico-mouse
+              vertico-unobtrusive
+              vertico-multiform)
+  (vertico-mouse-mode +1)
+  (vertico-multiform-mode +1)
+  (:option vertico-multiform-commands '((execute-extended-command flat)
+                                        (helpful-callable)
+                                        (helpful-variable))
+           ;; This is applied /after/ the above, so default is at the end of
+           ;; this alist.
+           vertico-multiform-categories '((file buffer grid)
+                                          (t unobtrusive)))
+  (dolist (buf-cmd '(consult-find
+                     consult-locate
+                     consult-grep
+                     consult-git-grep
+                     consult-ripgrep
+                     consult-line
+                     consult-line-multi
+                     consult-multi-occur
+                     consult-keep-lines
+                     consult-focus-lines))
+    (setf (alist-get buf-cmd vertico-multiform-commands) '(buffer)))
   (:with-map vertico-map
     (:bind "RET" #'vertico-directory-enter
            "DEL" #'vertico-directory-delete-char
-           "M-DEL" #'vertico-directory-delete-word))
+           "M-DEL" #'vertico-directory-delete-word
+           "TAB" #'+vertico-widen-or-complete))
   (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))
 
 (setup (:straight visual-fill-column)
diff --git a/lisp/+apheleia.el b/lisp/+apheleia.el
index f3b16e4..469232a 100644
--- a/lisp/+apheleia.el
+++ b/lisp/+apheleia.el
@@ -8,9 +8,8 @@
     (setq-local indent-line-function
                 (buffer-local-value 'indent-line-function orig))
     (indent-region (point-min)
-(point-max))
+                   (point-max))
     (funcall callback)))
 
 (provide '+apheleia)
 ;;; +apheleia.el ends here
- 
\ No newline at end of file
diff --git a/lisp/+casing.el b/lisp/+casing.el
index 5f39b2e..c8e9e4d 100644
--- a/lisp/+casing.el
+++ b/lisp/+casing.el
@@ -3,7 +3,6 @@
 ;;; Code:
 
 (require 'thingatpt)
-(require '+key)
 
 ;;;###autoload
 (defun +upcase-dwim (arg)
@@ -61,21 +60,23 @@ Otherwise, it calls `capitalize-word' on the word at point (using
 
 ;; Later on, I'll add repeat maps and stuff in here...
 
+(defvar +casing-map (let ((map (make-sparse-keymap)))
+                      (define-key map "u" #'+upcase-dwim)
+                      (define-key map (kbd "M-u") #'+upcase-dwim)
+                      (define-key map "l" #'+downcase-dwim)
+                      (define-key map (kbd "M-l") #'+downcase-dwim)
+                      (define-key map "c" #'+capitalize-dwim)
+                      (define-key map (kbd "M-c") #'+capitalize-dwim)
+                      map)
+  "Keymap for case-related twiddling.")
+
 (define-minor-mode +casing-mode
   "Enable easy case-twiddling commands."
   :lighter " cC"
+  :global t
   :keymap (let ((map (make-sparse-keymap)))
-            (define-key map "u" #'+upcase-dwim)
-            (define-key map (kbd "M-u") #'+upcase-dwim)
-            (define-key map "l" #'+downcase-dwim)
-            (define-key map (kbd "M-l") #'+downcase-dwim)
-            (define-key map "c" #'+capitalize-dwim)
-            (define-key map (kbd "M-c") #'+capitalize-dwim)
-            map)
-  (define-key +key-mode-map (kbd "M-c") (when +casing-mode
-                                          +casing-mode-map)))
-
-(defvaralias '+casing-map '+casing-mode-map)
+            (define-key map (kbd "M-c") +casing-map)
+            map))
 
 (provide '+casing)
 ;;; +casing.el ends here
diff --git a/lisp/+circe.el b/lisp/+circe.el
index c29cea6..3d6ea60 100644
--- a/lisp/+circe.el
+++ b/lisp/+circe.el
@@ -101,9 +101,11 @@ For entry into `lui-formatting-list'."
 
 (defun +circe-kill-buffer (&rest _)
   "Kill a circe buffer without confirmation, and after a delay."
-  (let ((circe-channel-killed-confirmation nil)
-        (circe-server-killed-confirmation nil))
-    (run-with-timer 0.25 nil 'kill-buffer)))
+  (let ((circe-channel-killed-confirmation)
+        (circe-server-killed-confirmation))
+    (when (derived-mode-p 'lui-mode)    ; don't spuriously kill
+      (ignore-errors
+        (kill-buffer)))))
 
 (defun +circe-quit@kill-buffer (&rest _)
   "ADVICE: kill all buffers of a server after `circe-command-QUIT'."
@@ -115,9 +117,11 @@ For entry into `lui-formatting-list'."
 
 (defun +circe-gquit@kill-buffer (&rest _)
   "ADVICE: kill all Circe buffers after `circe-command-GQUIT'."
-  (dolist (buf (circe-server-buffers))
-    (with-current-buffer buf
-      (+circe-quit@kill-buffer))))
+  (let ((circe-channel-killed-confirmation)
+        (circe-server-killed-confirmation))
+    (dolist (buf (circe-server-buffers))
+      (with-current-buffer buf
+        (+circe-quit@kill-buffer)))))
 
 (defun +circe-quit-all@kill-emacs ()
   "Quit all circe buffers when killing Emacs."
@@ -165,28 +169,104 @@ See `circe-network-options' for a list of common options."
                      (funcall +circe-server-buffer-action buffer))))
 
 ;;; Chat commands
-;; TODO: Actually ... write these~!?!?!
-
-(defun circe-command-SHORTEN (url)
-  "Shorten URL using `0x0-shorten-uri'.")
 
 (defun circe-command-SLAP (nick)
-  "Slap NICK around a bit with a large trout.")
-
-(defun circe-command-POKE (nick)
-  "Poke NICK like in the old Facebook days.")
-
-;;; Pure idiocy
-
-(define-minor-mode circe-cappy-hour-mode
+  "Slap NICK around a bit with a large trout."
+  (interactive (list (completing-read "Nick to slap: "
+                                      (circe-channel-nicks)
+                                      nil t nil)))
+  (circe-command-ME (format "slaps %s about a bit with a large trout" nick)))
+
+;;; Filtering functions
+;; Set `lui-input-function' to `+lui-filter', then add the filters you want to
+;; `circe-channel-mode-hook'.
+
+(require 'dash)
+
+(defvar +lui-filters nil
+  "Stack of input functions to apply.
+This is an alist with cells of the structure (TAG . FN), so we
+can easily remove elements.")
+(make-variable-buffer-local '+lui-filters)
+
+(defun +lui-filter (text &optional fn-alist)
+  (let ((fs (nreverse (purecopy (or fn-alist +lui-filters)))))
+    (while fs
+      (setq text (funcall (cdr (pop fs)) text)))
+    (circe--input text)))
+
+(defmacro +circe-define-filter (name docstring &rest body)
+  "Define a filter for circe-inputted text."
+  (declare (doc-string 2)
+           (indent 1))
+  (let (plist)
+    (while (keywordp (car-safe body))
+      (push (pop body) plist)
+      (push (pop body) plist))
+    ;; Return value
+    `(define-minor-mode ,name
+       ,docstring
+       ,@(nreverse plist)
+       (when (derived-mode-p 'circe-chat-mode)
+         (if ,name
+             (push '(,name . (lambda (it) ,@body)) +lui-filters)
+           (setq +lui-filters
+                 (assoc-delete-all ',name +lui-filters)))))))
+
+;; CAPPY HOUR! (Pure idiocy)
+
+(+circe-define-filter +circe-cappy-hour-mode
   "ENABLE CAPPY HOUR IN CIRCE!"
-  :lighter "CAPPY HOUR"
-  (when (derived-mode-p 'circe-chat-mode)
-    (if circe-cappy-hour-mode
-        (setq-local lui-input-function
-                    (lambda (input) (circe--input (upcase input))))
-      ;; XXX: It'd be better if this were more general, but whatever.
-      (setq-local lui-input-function #'circe--input))))
+  :lighter " CAPPY HOUR"
+  (upcase it))
+
+;; URL Shortener
+
+(+circe-define-filter +circe-shorten-url-mode
+  "Shorten long urls when chatting."
+  :lighter " c0x0"
+  (+circe-0x0-shorten-urls it))
+
+(defvar +circe-0x0-max-length 20
+  "Maximum length of URLs before using a shortener.")
+
+(defun +circe-0x0-shorten-urls (text)
+  "Find urls in TEXT and shorten them using `0x0'."
+  (require '0x0)
+  (require 'browse-url)
+  (let ((case-fold-search t))
+    (replace-regexp-in-string
+     browse-url-button-regexp
+     (lambda (match)
+       (if (> (length match) +circe-0x0-max-length)
+           (+with-message (format "Shortening URL: %s" match)
+             (0x0-shorten-uri (0x0--choose-server)
+                              (substring-no-properties match)))
+         match))
+     text)))
+
+;; Temperature conversion
+
+(+circe-define-filter +circe-F/C-mode
+  "Convert degF to degF/degC for international chats."
+  :lighter " F/C"
+  (str-F/C it))
+
+(defun fahrenheit-to-celsius (degf)
+  "Convert DEGF to Celsius."
+  (round (* (/ 5.0 9.0) (- degf 32))))
+
+(defun celsius-to-fahrenheit (degc)
+  "Convert DEGC to Fahrenheit."
+  (round (+ 32 (* (/ 9.0 5.0) degc))))
+
+(defun str-F/C (text)
+  (replace-regexp-in-string "[0-9.]+[Ff]"
+                            (lambda (match)
+                              (format "%s/%dC" match
+                                      (fahrenheit-to-celsius
+                                       (string-to-number match))))
+                            text))
 
 (provide '+circe)
 ;;; +circe.el ends here
diff --git a/lisp/+elfeed.el b/lisp/+elfeed.el
index c26bfab..4ee7581 100644
--- a/lisp/+elfeed.el
+++ b/lisp/+elfeed.el
@@ -21,12 +21,5 @@
         (scroll-down-command arg)
       (error (elfeed-show-prev)))))
 
-;; https://babbagefiles.blogspot.com/2017/03/take-elfeed-everywhere-mobile-rss.html
-(defun +elfeed ()
-  "Wrapper to load the elfeed db from disk before opening."
-  (interactive)
-  (elfeed-db-load)
-  (elfeed))
-
 (provide '+elfeed)
 ;;; +elfeed.el ends here
diff --git a/lisp/+embark.el b/lisp/+embark.el
index 3900492..e66d4b3 100644
--- a/lisp/+embark.el
+++ b/lisp/+embark.el
@@ -7,7 +7,6 @@
 ;;; Code:
 
 (require 'embark)
-(require 'marginalia)
 
 (embark-define-keymap embark-straight-map
   ("u" straight-visit-package-website)
@@ -22,7 +21,8 @@
 
 (add-to-list 'embark-keymap-alist '(straight . embark-straight-map))
 
-(add-to-list 'marginalia-prompt-categories '("recipe\\|package" . straight))
+(with-eval-after-load 'marginalia
+ (add-to-list 'marginalia-prompt-categories '("recipe\\|package" . straight)))
 
 (provide '+embark)
 ;;; +embark.el ends here
diff --git a/lisp/+lookup.el b/lisp/+lookup.el
deleted file mode 100644
index 755f84e..0000000
--- a/lisp/+lookup.el
+++ /dev/null
@@ -1,26 +0,0 @@
-;;; +lookup.el                                       -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; I look up a lot of things in Emacs.  Let's bind them all to an easy-to-use
-;; keymap.
-
-;;; Code:
-
-(require '+key)
-
-(define-minor-mode +lookup-mode
-  "A mode for easily looking things up."
-  :lighter " l^"
-  :keymap (let ((map (make-sparse-keymap)))
-             (define-key map "f" #'find-function)
-             (define-key map "l" #'find-library)
-             (define-key map "v" #'find-variable)
-             map)
-  (define-key +key-mode-map (kbd "C-c l") (when +lookup-mode
-                                            +lookup-mode-map)))
-
-(defvaralias '+lookup-map '+lookup-mode-map)
-
-(provide '+lookup)
-;;; +lookup.el ends here
diff --git a/lisp/+org-capture.el b/lisp/+org-capture.el
index ba036bd..6c59b98 100644
--- a/lisp/+org-capture.el
+++ b/lisp/+org-capture.el
@@ -85,5 +85,21 @@ properly process the variable."
     ;; Sort after, maybe
     (when sort-after (+org-capture-sort list))))
 
+(defun +org-template--ensure-path (keys &optional list)
+  "Ensure path of keys exists in `org-capture-templates'."
+  (unless list (setq list 'org-capture-templates))
+  (when (> (length key) 1)
+    ;; Check for existence of groups.
+    (let ((expected (cl-loop for i from 1 to (1- (length key))
+                             collect (substring key 0 i) into keys
+                             finally return keys)))
+      (cl-loop for ek in expected
+               if (not (+org-capture--get ek (symbol-value list))) do
+               (setf (+org-capture--get ek (symbol-value list))
+                     (list (format "(Group %s)" ek)))))))
+
+(defun +org-define-capture-template (keys title &rest args)
+  )
+
 (provide '+org-capture)
 ;;; +org-capture.el ends here
diff --git a/lisp/+vertico.el b/lisp/+vertico.el
index 4adde3d..d4fb3a3 100644
--- a/lisp/+vertico.el
+++ b/lisp/+vertico.el
@@ -12,5 +12,13 @@
     (unless (eq 1 (abs (- beg-index vertico--index)))
       (ding))))
 
+(defun +vertico-widen-or-complete ()
+  (interactive)
+  (if (or vertico-unobtrusive-mode
+          vertico-flat-mode)
+      (progn (vertico-unobtrusive-mode -1)
+             (vertico-flat-mode -1))
+    (call-interactively #'vertico-insert)))
+
 (provide '+vertico)
 ;;; +vertico.el ends here
diff --git a/lisp/acdw.el b/lisp/acdw.el
index 262c15e..603f46f 100644
--- a/lisp/acdw.el
+++ b/lisp/acdw.el
@@ -128,10 +128,12 @@ I keep forgetting how they differ."
 (defmacro +with-message (message &rest body)
   "Execute BODY, with MESSAGE.
 If body executes without errors, MESSAGE...Done will be displayed."
-  ;; ^ TODO
-  `(prog1 (progn (message ,(concat message "..."))
-                 ,@body)
-          (message ,(concat message "...Done."))))
+  (declare (indent 1))
+  (let ((msg (gensym)))
+    `(let ((,msg ,message))
+       (unwind-protect (progn (message "%s..." ,msg)
+                              ,@body)
+         (message "%s... Done." ,msg)))))
 
 (defun +mapc-some-buffers (func &optional predicate)
   "Perform FUNC on all buffers satisfied by PREDICATE.
-- 
cgit 1.4.1-21-gabe81