;;; init.el --- Emacs initiation file                 -*- lexical-binding: t -*-

;; Author: Case Duckworth <acdw@acdw.net>
;; Created: Sometime during Covid-19, 2020
;; Keywords: configuration
;; URL: https://tildegit.org/acdw/emacs
;; Bankruptcy: 8

;;; License:

;; Everyone is permitted to do whatever they like with this software
;; without limitation.  This software comes without any warranty
;; whatsoever, but with two pieces of advice:
;; - Be kind to yourself.
;; - Make good choices.

;;; Code:

(let ((early-features `((early-init . ,(locate-user-emacs-file "early-init"))
                        acdw private +key)))
  (dolist (feature early-features)
    (require (or (car-safe feature) feature) (cdr-safe feature) :noerror)))

(setup (:require +casing)
  (:global "M-u" #'universal-argument)
  (+casing-mode +1))

(setup (:require +emacs)
  ;; +emacs.el contains super-basic defaults that are basically necessary for
  ;; good functioning.  In this block, I add extra things or more "experimental"
  ;; ones that might not belong in a separate file.
  (:also-load +lisp)
  (:option truncate-string-ellipsis "…")
  ;; Bindings
  (:global "C-x C-k" #'kill-current-buffer
           "C-x 4 n" #'clone-buffer
           "C-c v" #'visible-mode
           "C-M-;" #'+lisp-comment-or-uncomment-sexp
           "M-j" nil
           "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-m" #'execute-extended-command  ; original: coding systems
           "C-<backspace>" #'+backward-kill-word
           "C-x TAB" #'+indent-rigidly
           ;; Alright, Yegge... NOPE! C-x t is tab prefix ... (maybe F6 or something?)
           ;; "C-x t" #'beginning-of-buffer
           ;; "C-x e" #'end-of-buffer
           )
  ;; 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-?)
  ;; Faces
  (dolist (face '(line-number
                  line-number-major-tick
                  line-number-minor-tick
                  line-number-current-line))
    (:face face '((t (:inherit fixed-pitch)))))
  ;; Hooks
  (add-hook 'prog-mode-hook #'turn-on-auto-fill)
  (add-hook 'prog-mode-hook #'font-lock-todo-insinuate)
  (add-hook 'text-mode-hook #'turn-on-auto-fill)
  (add-hook 'special-mode-hook #'turn-off-auto-fill)
  ;; Advice
  ;; https://old.reddit.com/r/emacs/comments/rlli0u/whats_your_favorite_defadvice/hph14un/
  (define-advice keyboard-escape-quit (:around (fn &rest r))
    "Don't close splits on `keyboard-escape-quit'."
    (let ((buffer-quit-function #'ignore))
      (apply fn r))))

(setup (:require +init)
  (:local-hook user-save-hook #'+init-sort)
  (+with-ensure-after-init
    (:hook #'+init-add-setup-to-imenu)))

(setup (:require auth-source)
  (:option auth-sources (list 'default
                              "secrets:passwords"
                              (private/ "authinfo")))
  (:with-mode authinfo-mode
    (:local-set truncate-lines t)))

(setup (:require autoinsert)
  (auto-insert-mode +1))

(setup (:require autoinsert)
  (setf (alist-get "\\.scm" auto-insert-alist nil nil #'equal)
        '(insert "#!/bin/sh\n#| -*- scheme -*-\nexec csi -s $0 \"$@\"\n|#\n"))
  ;;  (auto-insert-mode +1)
  )

(setup (:require cus-edit)
  ;; I don't use Custom to actually /make/ any customizations, but it's handy to
  ;; (A) see what options are available and (B) persist some changes across
  ;; restarts, for example, `safe-local-variables'.
  (:require +cus-edit)
  (:option custom-file (private/ "custom.el")
           custom-magic-show nil
           custom-magic-show-button t
           custom-raised-buttons nil
           custom-unlispify-tag-names nil
           custom-variable-default-form 'lisp)
  (dolist (var '(safe-local-variable-values
                 warning-suppress-types))
    (add-to-list '+custom-variable-allowlist var))
  (+ensure-after-init
   (+custom-load-ignoring-most-customizations))
  (advice-add #'custom-buffer-create-internal :after #'+cus-edit-expand-widgets)
  (:with-mode Custom-mode
    (:local-set imenu-generic-expression +cus-edit-imenu-generic-expression)))

(setup (:require goto-addr)
  (if (fboundp #'global-goto-address-mode)
      (global-goto-address-mode)
    (add-hook 'after-change-major-mode-hook #'goto-address-mode)))

(setup (:require pulse)
  (:also-load +pulse)
  (:option pulse-flag nil
           pulse-delay 0.5
           pulse-iterations 1)
  (dolist (command '(+ace-window-or-switch-buffer
                     pop-mark pop-global-mark
                     Info-history-back Info-history-forward
                     ))
    (add-to-list '+pulse-location-commands command))
  (+ensure-after-init #'+pulse-location-mode))

(setup (:require reading)
  ;;(:hook-into view-mode)                ; XXX doesn't go back
  )

(setup (:require user-save)
  (add-hook 'user-save-hook #'+clean-empty-lines)
  (add-hook 'user-save-hook (defun user-save@save-some-buffers ()
                              (save-some-buffers t t)))
  (user-save-global-mode +1))

(setup (:require winner)
  (winner-mode +1))

(setup +key
  (+ensure-after-init #'+key-global-mode))

(setup _work
  (+with-ensure-after-init
    (require '_work)))

(setup abbrev
  (:option abbrev-file-name (sync/ "abbrev.el")
           save-abbrevs 'silent)
  (with-eval-after-load 'user-save
    (:with-mode edit-abbrevs-mode
      (:hook #'turn-off-user-save-mode)))
  (:hook-into text-mode
              circe-chat-mode))

(setup autorevert
  (:option global-auto-revert-non-file-buffers t
           auto-revert-verbose nil)
  (global-auto-revert-mode +1))

(setup bookmark
  (:option bookmark-save-flag 1
           bookmark-watch-bookmark-file 'silent))

(setup browse-url
  (:require +browse-url)
  (: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-chrome-program (seq-some #'executable-find
                                       '("chromium"
                                         "chrome"
                                         "google-chrome-stable"))
   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)
   browse-url-new-window-flag nil
   browse-url-firefox-arguments '("--new-tab")
   browse-url-firefox-new-window-is-tab t)
  (defvar +invidious-host
    ;; TODO: Add variables for other transformations and what-not.
    ;; ... or enable trying multiple servers
    "invidious.snopyta.org"
    "Host for invidious instance.")
  ;; Set up external browsing URLs.
  (add-to-list '+custom-variable-allowlist
               '+browse-url-secondary-browser-regexps)
  (dolist (domain '("github.com" "gitlab.com" "google.com"
                    "imgur.com" "twitch.tv"
                    "pixelfed" "instagram.com" "bibliogram.art"
                    "reddit.com" "teddit.net"
                    "twitter.com" "nitter.net" "t.co"
                    "streamable.com" "spotify.com"
                    "hetzner.cloud"
                    "melpa.org"))
    (add-to-list '+browse-url-secondary-browser-regexps
                 (replace-regexp-in-string "\\." "\\\\." domain)))
  ;; Set up URL handlers.
  (:option browse-url-handlers
           (list
            (cons (rx                           ; images
                   "." (or "jpeg" "jpg" "png" "bmp") eos)
                  (lambda (&rest args)
                    (apply
                     (cond ((executable-find "mpv") #'+browse-image-with-mpv)
                           (t #'eww-browse-url))
                     args)))
            (cons (rx (or ;; videos
                       "youtube.com" "youtu.be" "invidious" "yewtu.be"
                       (seq "." (or "mp4" "gif" "mov" "MOV" "webm") eos)
                       ;; music
                       "soundcloud.com" "bandcamp.com"
                       (seq "." (or "ogg" "mp3" "opus" "m4a") eos)))
                  (lambda (&rest args)
                    (apply (if (executable-find "mpv")
                               #'+browse-url-with-mpv
                             browse-url-secondary-browser-function)
                           args)))
            (cons (+browse-url-secondary-browser-regexps-combine) ; non-text websites
                  (lambda (&rest args)
                    (apply browse-url-secondary-browser-function args)))
            (cons "xkcd\\.com"
                  (lambda (&rest args)
                    (apply (if (fboundp #'xkcd-get)
                               (progn (require '+xkcd)
                                      #'+xkcd-get-from-url)
                             +browse-url-browser-function)
                           args)))
            (cons "."                           ; everything else
                  (lambda (&rest args)
                    (apply +browse-url-browser-function args)))))
  (with-eval-after-load 'chd
    (add-to-list 'browse-url-handlers
                 (cons chd/url-regexps #'browse-url-chrome)))
  ;; Transform URLs before passing to `browse-url'
  (:option +browse-url-transformations `((,(rx (or "youtube.com"
                                                   "youtu.be"))
                                          . ,+invidious-host)
                                         ("twitter\\.com"
                                          . "nitter.net")
                                         ("instagram\\.com"
                                          . "bibilogram.art")
                                         (,(rx (or "reddit.com"
                                                   "old.reddit.com"))
                                          . "teddit.net")
                                         ("medium\\.com"
                                          . "scribe.rip")
                                         ("www\\.npr\\.org"
                                          . "text.npr.org")))
  (+browse-url-transform-url-global-mode +1))

(setup calendar
  (require '_location)
  (:option diary-file (private/ "diary")))

(setup compile
  (:require +compile)
  (:+key "<f5>" #'+compile-dispatch)
  (:option compilation-always-kill t
           compilation-ask-about-save nil
           compilation-scroll-output t))

(setup dired
  (:require dired-x +dired)
  (:straight dired+)
  (:option dired-recursive-copies 'always
           dired-recursive-deletes 'always
           dired-create-destination-dirs 'always
           dired-do-revert-buffer t
           dired-hide-details-hide-symlink-targets nil
           dired-isearch-filenames 'dwim
           delete-by-moving-to-trash t
           dired-auto-revert-buffer t
           dired-listing-switches "-AlF"
           ls-lisp-dirs-first t
           dired-ls-F-marks-symlinks t
           dired-clean-confirm-killing-deleted-buffers nil
           dired-no-confirm '(byte-compile
                              load chgrp chmod chown
                              copy move hardlink symlink
                              shell touch)
           dired-dwim-target t)
  (:local-set truncate-lines t)
  (:bind "<backspace>" #'dired-up-directory
         "j" #'+dired-goto-file)
  (:hook #'dired-hide-details-mode
         #'hl-line-mode
         #'lin-mode
         #'+dired-dim-git-ignores)
  (:+key "C-x C-j" #'dired-jump)
  (dolist (refresh-after-func '(dired-do-flagged-delete))
    (advice-add refresh-after-func :after #'revert-buffer))
  (with-eval-after-load 'frowny
    (add-to-list 'frowny-inhibit-modes #'dired-mode)))

(setup eldoc
  (:hook-into elisp-mode
              lisp-interaction-mode))

(setup elisp-mode
  (:also-load +elisp)
  (:option eval-expression-print-length nil
           eval-expression-print-level nil)
  (:with-map (emacs-lisp-mode-map lisp-interaction-mode-map)
    (:bind "C-c C-c" #'eval-defun
           "C-c C-k" #'+elisp-eval-region-or-buffer
           "C-c C-z" #'ielm))
  (advice-add #'eval-region :around #'+eval-region@pulse))

(setup eshell
  (:also-load em-smart
              em-tramp)
  (:require +eshell)
  (+define-dir eshell/ (locate-user-emacs-file "eshell")
    "Where to place Eshell-specific files.")
  (:option eshell-aliases-file (eshell/ "aliases")
           ;; What are these for???
           eshell-rc-script (eshell/ "profile")
           eshell-login-script (eshell/ "login")
           eshell-destroy-buffer-when-process-dies t
           eshell-directory-name eshell/
           eshell-error-if-no-glob t
           eshell-hist-ignore-dups t
           eshell-kill-on-exit nil
           eshell-prefer-lisp-functions t
           eshell-prefer-lisp-variables t
           eshell-review-quick-commands nil
           eshell-save-history-on-exit t
           eshell-scroll-to-bottom-on-input 'all
           eshell-smart-space-goes-to-end t
           eshell-where-to-jump 'begin
           eshell-banner-message ""
           eshell-prompt-regexp (rx bol (* (not (any ?# ?$ ?\n)))
                                    " " (any ?# ?$)
                                    (* " ")))
  (:+leader "s" #'+eshell-here
            "C-s" #'+eshell-here)
  (with-eval-after-load 'mwim
    (setf (alist-get 'eshell-mode mwim-beginning-of-line-function)
          #'eshell-bol))
  (+eshell-eval-after-load
    ;; Local modes
    (dolist (mode '((hungry-delete-mode . -1)))
      (funcall (car mode) (cdr mode)))
    ;; Set local settings
    (dolist (setting `((outline-regexp . ,eshell-prompt-regexp)
                       (page-delimiter . ,eshell-prompt-regexp)
                       (imenu-generic-expression "Prompt"
                                                 ,(concat eshell-prompt-regexp
                                                          "\\(.*\\)")
                                                 1)
                       (truncate-lines . t)
                       (scroll-margin . 0)))
      (set (make-local-variable (car setting)) (cdr setting)))
    ;; Bind keys
    (dolist (binding '(("C-d" . +eshell-quit-or-delete-char)))
      (define-key eshell-mode-map
        (kbd (car binding)) (cdr binding)))
    ;; Environment variables
    (dolist (environment '(("PAGER" . "cat")))
      (setenv (car environment) (cdr environment)))))

(setup eww
  (:also-load +eww)
  (:option eww-search-prefix "https://duckduckgo.com/html?q="
           url-privacy-level '(email agent cookies lastloc))
  (add-hook 'eww-after-render-hook #'reading-mode)
  (:hook #'+eww-bookmark-setup
         #'+eww-track-readable-mode)
  (:bind "b" #'bookmark-set
         "B" #'bookmark-jump
         "M-n" nil
         "M-p" nil))

(setup flyspell
  (:hook-into org-mode))

(setup hideshow
  (:also-load +hideshow)
  (:with-mode hs-minor-mode
    (:hook-into prog-mode)
    (:bind "C-<tab>" #'+hs-cycle
           "C-S-<tab>" #'+hs-global-cycle
           ;; but y tho
           "C-S-<iso-lefttab>" #'+hs-global-cycle)))

(setup ibuffer
  (:also-load ibuf-ext)
  (:option ibuffer-expert t
           ibuffer-show-empty-filter-groups nil
           ibuffer-saved-filter-groups
           '(("default"
              ("Org" (mode . org-mode))
              ("emacs" (or (name . "^\\*scratch\\*$")
                           (name . "^\\*Messages\\*$")
                           (name . "^\\*Warnings\\*$")
                           (name . "^\\*straight-process\\*$")
                           (name . "^\\*Calendar\\*$")))
              ("customize" (mode . Custom-mode))
              ("emacs-config" (or (filename . ".emacs.d")
                                  (mode . +init-mode)))
              ("git" (or (name . "^\*magit")
                         (name . "^\magit")))
              ("help" (or (mode . help-mode)
                          (mode . Info-mode)
                          (mode . helpful-mode)))
              ("irc" (or (mode . erc-mode)
                         (mode . circe-server-mode)
                         (mode . circe-channel-mode)))
              ("shell" (or (mode . eshell-mode)
                           (mode . shell-mode)
                           (mode . vterm-mode)))
              ("web" (or (mode . elpher-mode)
                         (mode . eww-mode))))))
  (:hook (defun ibuffer@filter-to-default ()
           (ibuffer-auto-mode +1)
           (ibuffer-switch-to-saved-filter-groups "default"))))

(setup info
  (:also-load +Info)
  (dolist (dir (split-string (getenv "INFOPATH") ":"  t))
    (add-to-list 'Info-additional-directory-list dir))
  (:with-mode Info-mode                 ; -_-
    (:hook #'reading-mode)
    (:bind "c" #'+Info-copy-current-node-name
           "w" #'+Info-copy-current-node-name)))

(setup ispell
  (:also-load +ispell)
  (:option ispell-program-name (or (executable-find "ispell")
                                   (executable-find "aspell")))
  (put 'ispell-buffer-session-localwords
       'safe-local-variable #'+ispell-safe-local-p)
  (add-hook 'user-save-hook #'+ispell-move-buffer-words-to-dir-locals-hook))

(setup kmacro
  (:also-load +kmacro)
  (with-eval-after-load '+kmacro
    ;; (+kmacro-recording-indicator-mode +1)
    (+kmacro-block-undo-mode +1)))

(setup minibuffer
  (:require +minibuffer)
  (:with-map minibuffer-local-map
    (:bind "M-/" #'+minibuffer-complete-history)))

(setup mouse
  ;; Brand new for Emacs 28: see https://ruzkuku.com/texts/emacs-mouse.html
  ;; Actually, look at this as well: https://www.emacswiki.org/emacs/Mouse3
  (when (fboundp 'context-menu-mode)
    (:option context-menu-functions
             '(context-menu-ffap
               context-menu-region
               context-menu-undo
               context-menu-dictionary))
    (context-menu-mode +1))
  (dolist (click '(;; Fix scrolling in the margin
                   wheel-down double-wheel-down triple-wheel-down
                   wheel-up double-wheel-up triple-wheel-up))
    (global-set-key (vector 'right-margin click) 'mwheel-scroll)
    (global-set-key (vector 'left-margin click) 'mwheel-scroll)))

(setup net-utils
  (:require +finger)                        ; fixes `finger' to use var below
  (:option finger-X.500-host-regexps '(".") ; only send username
           ))

(setup notmuch
  (:load-from "~/usr/share/emacs/site-lisp/")
  (:load-after bbdb)
  (:also-load +notmuch +message)
  (+define-dir notmuch/ (sync/ "emacs/notmuch")
    "Notmuch configuration and data.")
  (:option notmuch-init-file (notmuch/ "notmuch-init.el" t)
           notmuch-address-save-filename (notmuch/ "addresses" t)
           notmuch-address-use-company (featurep 'company)
           notmuch-search-oldest-first nil
           notmuch-archive-tags '("-inbox" "-unread"))
  ;; Reading mail
  (:option notmuch-show-indent-content nil)
  (add-hook 'notmuch-show-mode-hook #'visual-fill-column-mode)
  ;; Composing mail
  (:option message-kill-buffer-on-exit t
           message-auto-save-directory "~/var/mail/drafts")
  ;; Sending mail
  (:option send-mail-function #'sendmail-send-it
           mail-specify-envelope-from t
           message-sendmail-envelope-from 'header
           mail-envelope-from 'header)
  ;; Extras and fixes
  (with-eval-after-load 'notmuch
    (load notmuch-init-file :noerror)
    (add-hook 'message-setup-hook #'+message-signature-setup)
    (add-hook 'message-send-hook #'+send-mail-dispatch)
    (advice-add 'notmuch-tag :filter-args #'+notmuch-correct-tags)
    (:option notmuch-saved-searches (list
                                     (list :name "inbox"
                                           :query (+notmuch-query-concat "tag:inbox"
                                                                         "NOT tag:Spam")
                                           :key "i")
                                     (list :name "lists"
                                           :query (+notmuch-query-concat "tag:/List/"
                                                                         "tag:unread")
                                           :key "l")
                                     (list :name "unread"
                                           :query (+notmuch-query-concat "tag:unread"
                                                                         "NOT tag:Spam")
                                           :key "u")
                                     (list :name "flagged" :query "tag:flagged" :key "f")
                                     (list :name "sent" :query "tag:sent" :key "t")
                                     (list :name "drafts" :query "tag:draft" :key "d")
                                     (list :name "all mail" :query "*" :key "a"))))
  (:+leader "m" #'+notmuch-goto "C-m" #'+notmuch-goto
            "n" #'notmuch "C-n" #'notmuch)
  ;; For `focus'
  (put 'notmuch-message 'bounds-of-thing-at-point 'notmuch-show-message-extent))

(setup org
  ;; Plain org with the `setup' form for sorting, but I install with straight.
  (:straight (org
              :type git :host nil
              :repo "https://git.savannah.gnu.org/git/emacs/org-mode.git"
              :local-repo "org"
              :depth full
              :pre-build (straight-recipes-org-elpa--build)
              :build (:not autoloads)
              :files (:defaults
                      "lisp/*.el"
                      ("etc/styles/" "etc/styles/*"))))
  (:straight (org-contrib
              :type git :host nil
              :repo "https://git.sr.ht/~bzg/org-contrib"))
  ;; DO NOT load system-installed org !!!
  (setq load-path
        (cl-remove-if (lambda (path) (string-match-p "lisp/org\\'" path)) load-path))
  (:also-load +org)
  (:option org-adapt-indentation nil
           org-auto-align-tags t
           org-archive-mark-done t
           org-catch-invisible-edits 'show-and-error
           org-clock-clocked-in-display 'mode-line
           org-clock-frame-title-format (cons
                                         '(t org-mode-line-string)
                                         (cons " --- " frame-title-format))
           org-clock-string-limit 7     ; just the clock bit
           ;; org-clock-string-limit 25    ; gives enough information
           org-clock-persist t
           org-confirm-babel-evaluate nil
           org-cycle-separator-lines 0
           org-directory (sync/ "org/" t)
           org-ellipsis truncate-string-ellipsis
           org-fontify-done-headline t
           org-fontify-quote-and-verse-blocks t
           org-fontify-whole-heading-line t
           org-hide-emphasis-markers t
           org-html-coding-system 'utf-8-unix
           org-image-actual-width (list (* (window-font-width)
                                           (- fill-column 8)))
           org-imenu-depth 3
           org-indent-indentation-per-level 0
           org-indent-mode-turns-on-hiding-stars nil
           org-insert-heading-respect-content t
           org-list-demote-modify-bullet '(("-" . "+")
                                           ("+" . "-"))
           org-log-done 'time
           org-log-into-drawer t
           org-num-skip-commented t
           org-num-skip-unnumbered t
           org-num-skip-footnotes t
           org-outline-path-complete-in-steps nil
           org-pretty-entities t
           org-pretty-entities-include-sub-superscripts nil
           org-refile-targets '((nil . (:maxlevel . 2))
                                (org-agenda-files . (:maxlevel . 1)))
           org-refile-use-outline-path 'file
           org-special-ctrl-a/e t
           org-special-ctrl-k t
           org-src-fontify-natively t
           org-src-tab-acts-natively t
           org-src-window-setup 'current-window
           org-startup-truncated nil
           org-startup-with-inline-images t
           org-tags-column 1 ;; (- (- fill-column (length org-ellipsis)))
           org-todo-keywords '((sequence "TODO(t)" "WAIT(w@/!)" "ONGOING(o@)"
                                         "|" "DONE(d!)")
                               (sequence "|" "CANCELED(k@)")
                               (sequence "MEETING(m)")
                               (sequence "ASSIGNED(a@/!)" "REVIEW(r)" "|" "DONE(d!)"))
           org-use-speed-commands t
           org-emphasis-alist '(("*" org-bold)
                                ("/" org-italic)
                                ("_" org-underline)
                                ("=" org-verbatim)
                                ("~" org-code)
                                ("+" org-strikethrough)))
  ;; (setq org-todo-keywords
  ;;       '((sequence
  ;;          "TODO(t)"
  ;;          "NEXT(n!)" ; next action
  ;;          "DONE(d)"  ; done)
  ;;          (sequence
  ;;           "WAIT(w@)"   ; waiting to be actionable again
  ;;           "HOLD(h@/!)" ; actinable, but will do later
  ;;           "IDEA(i)"    ; maybe someday
  ;;           "KILL(k@/!)" ;  cancelled, aborted or is no longer applicable
  ;;           ))))
  (:bind "RET" #'+org-return-dwim
         "<S-return>" #'+org-table-copy-down
         "C-c C-l" #'+org-insert-link-dwim
         "C-c C-n" #'+org-next-heading-widen
         "C-c C-p" #'+org-previous-heading-widen
         "C-c C-o" #'+org-open-at-point-dwim
         "`" #'+org-insert-tilde
         "~" #'+org-insert-backtick)
  (:global [f8] #'org-clock-in
           [f9] #'org-clock-out
           "C-c l" #'org-store-link)
  (:hook #'variable-pitch-mode
         #'turn-off-auto-fill
         #'org-indent-mode
         #'prettify-symbols-mode
         #'+org-wrap-on-hyphens)
  (:local-set prettify-symbols-alist '(("DEADLINE:" . ?→)
                                       ("SCHEDULED:" . ?↷)
                                       ("CLOSED:" . ?✓))
              ;;+modeline-position-function #'+org-count-words-stupidly
              )
  (:local-hook user-save-hook #'+org-before-save@prettify-buffer)
  (advice-add #'org-delete-backward-char :override #'+org-delete-backward-char)
  ;; (define-advice org-open-at-point (:around (fn &rest r) open-external)
  ;;   "Open links from org externally."
  ;;   (let ((browse-url-browser-function browse-url-secondary-browser-function))
  ;;     (apply fn r)))
  ;; (add-to-list '+custom-variable-allowlist 'org-agenda-files)
  (with-eval-after-load 'org
    (setf (alist-get "\\.x?html?\\'" org-file-apps nil nil #'equal)
          #'+org-open-html)
    (org-clock-persistence-insinuate)
    (org-link-set-parameters "tel" :follow #'+org-tel-open)
    (org-link-set-parameters "sms" :follow #'+org-sms-open)
    (setf (alist-get "\\.x?html?\\'" org-file-apps nil nil #'equal)
          #'+org-open-html))
  ;; Extra keywords
  (font-lock-add-keywords
   'org-mode
   '(;; Fancy list bullets
     ;; NOTE: these `progn' and `default's are necessary; otherwise Emacs
     ;; complains about "Invalid face reference: t" in org-mode buffers, because
     ;; `compose-region' returns t.
     ("^[ \t]*\\([-]\\) "
      (0 ;; (progn (compose-region (match-beginning 1) (match-end 1) "–") 'fixed-pitch)
       'fixed-pitch t))
     ("^[ \t]*\\([+]\\) "
      (0 ;; (progn (compose-region (match-beginning 1) (match-end 1) "¬") 'fixed-pitch)
       'fixed-pitch t))
     ("^[ \t]+\\([*]\\) "
      (0 ;; (progn (compose-region (match-beginning 1) (match-end 1) "→") 'fixed-pitch)
       'fixed-pitch t))
     ;; Fancy numbered lists (well, monospaced)
     ("^[ \t]*\\(\\(?:[0-9]+\\|[A-Za-z]\\)[.)]\\) " 0 'fixed-pitch t)
     ;; Make leading org-heading stars fixed-pitch
     ("^\*+ " 0 'fixed-pitch t)
     ))
  (with-eval-after-load 'form-feed
    ;; Horizontal lines
    (font-lock-add-keywords
     'org-mode
     '(("^-----+" . form-feed--font-lock-face))))
  (put 'browse-url-browser-function 'safe-local-variable
       (lambda (val)
         (eq (function-get val 'browse-url-browser-kind :autoload)
             'external))))

(setup org-agenda
  (:option org-agenda-skip-deadline-if-done t
           org-agenda-skip-scheduled-if-done t
           org-agenda-span 10
           org-agenda-block-separator ?─
           org-agenda-time-grid
           '((daily today require-timed)
             (800 1000 1200 1400 1600 1800 2000)
             " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")
           org-agenda-current-time-string
           "← now ─────────────────────────────────────────────────"
           org-agenda-include-diary nil ; I use the org-diary features
           org-agenda-todo-ignore-deadlines 'near
           org-agenda-todo-ignore-scheduled 'future
           org-agenda-include-deadlines t
           org-deadline-warning-days 0
           org-agenda-show-future-repeats 'next
           org-agenda-window-setup 'current-window)
  (unless after-init-time
    (:option org-agenda-files (list (sync/ "org/"))))
  (dolist (var '(org-agenda-files
                 org-agenda-file-regexp
                 org-agenda-templates))
    (add-to-list '+custom-variable-allowlist var))
  (define-advice org-agenda-files (:filter-return (ret))
    "Remove SyncThing's sync-conflict files from the org agenda."
    (seq-remove (lambda (f) (string-match-p "sync-conflict" f)) ret))
  (:+leader "a" #'org-agenda "C-a" #'org-agenda)
  (:hook #'hl-line-mode)
  (:local-set truncate-lines t)
  (add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree))

(setup org-attach
  (:also-load +org-attach)
  (:option org-attach-method 'lns)
  (with-eval-after-load '+org-attach
    (+org-attach-fix-args-mode +1)))

(setup org-capture
  (:require +org-capture)
  (:+leader "c" #'org-capture "C-c" #'org-capture)
  (+org-capture-templates-setf "t" "Todo")
  (+org-capture-templates-setf "tt"
      `("Today!" entry (file "todo.org")
        ,(concat "* TODO %^{Title}\n"
                 "DEADLINE: %t\n"
                 "\n%?")))
  (+org-capture-templates-setf "ts"
      `("Someday..." entry (file "todo.org")
        ,(concat "* TODO %^{Title}\n"
                 ":PROPERTIES:\n"
                 ":CREATED: [%<%F %T>]\n"
                 ":END:\n"
                 "\n%?")))
  (+org-capture-templates-setf "tm"
      `("Media" entry (file "todo.org")
        ,(concat "* TODO %^{TITLE}\n"
                 ":PROPERTIES:\n"
                 ":TITLE: %\\1\n"
                 ":AUTHOR: %^{AUTHOR}\n"
                 ":END:\n"
                 "\n%?")))
  (+org-capture-templates-setf "l"
      `("Link" entry (file "links.org")
        "* %(+org-insert-link-dwim) %^g\n\n"))
  (+org-capture-templates-setf "w" "Work")
  (+org-capture-templates-setf "j"
      '("Journal entry" plain
        (file+olp+datetree "journal.org")
        "**** %U\n%i\n%?"))
  ;; TODO: Prompt for identity file from ~/.ssh and try to guess the hostname
  ;; from there.
  (+org-capture-templates-setf "s"
      `("SSH Config" plain (file "~/.ssh/config")
        ,(concat "\n\nHost %^{Host: }"
                 "\nHostname %\\1"
                 "\nUser %^{User:|" (user-login-name) "}"
                 "\nIdentityFile %?"
                 "\nIdentitiesOnly yes"
                 "\nPubkeyAuthentication yes"
                 "\nPort %^{Port: |22}")
        :unnarrowed t))
  (+org-capture-sort))

(setup org-id
  (:load-after org)
  ;; https://helpdeskheadesk.net/2022-03-13/
  (:option org-id-method 'ts
           org-attach-id-to-path-function-list '(org-attach-id-ts-folder-format
                                                 org-attach-id-uuid-folder-format)))

(setup ox                               ; org-export
  (:also-load +ox
              ox-md)
  (:option org-export-coding-system 'utf-8-unix
           org-export-headline-levels 8
           org-export-with-drawers nil
           org-export-with-section-numbers nil
           org-export-with-smart-quotes t
           org-export-with-sub-superscripts t
           org-export-with-toc nil)
  (with-eval-after-load 'ox
    (+org-export-pre-hooks-insinuate))
  (add-hook '+org-export-pre-hook #'+flyspell-correct-buffer)
  (with-eval-after-load 'user-save
    (add-hook '+org-export-pre-hook #'user-save-run-hooks)))

(setup password-cache
  (:option password-cache t
           password-cache-expiry (* 60 60)))

(setup prettify-symbols-mode
  (:option prettify-symbols-unprettify-at-point t))

(setup prog
  (:local-set comment-auto-fill-only-comments t)
  (:hook #'prettify-symbols-mode))

(setup scratch
  (:require +scratch)
  (:option initial-major-mode #'lisp-interaction-mode
           initial-scratch-message (+scratch-fortune))
  (add-hook 'kill-buffer-query-functions #'+scratch-immortal))

(setup shr
  (:also-load +shr)
  (:option shr-width (- fill-column 5)  ; pad out for wide letters
           shr-use-fonts t)
  (dolist (mode '(eww-mode
                  elfeed-show-mode))
    (add-hook (intern (format "%s-hook" mode)) #'+shr-heading-setup-imenu)))

(setup tab-bar
  (:require +tab-bar)
  (:option tab-bar-tab-name-function '+tab-bar-basename
           tab-bar-tab-name-truncated-max 20
           tab-bar-tab-name-ellipsis truncate-string-ellipsis
           tab-bar-show t
           tab-bar-close-button-show t
           tab-bar-new-button-show t
           +tab-bar-menu-bar-icon " ; "
           tab-bar-close-button (propertize " × "
                                            'display t
                                            'close-tab nil)
           tab-bar-new-button (propertize "+ " 'display t))
  ;; I need to set these here so that they take effect /before/ `display-time-mode'
  (:option display-time-format "%H:%M"
           display-time-mail-file :disable
           display-time-load-average-threshold 50)
  (:option tab-bar-format '(;;+tab-bar-format-menu-bar
                            tab-bar-format-history
                            tab-bar-format-tabs
                            tab-bar-separator
                            tab-bar-format-add-tab
                            +tab-bar-format-align-right
                            ;;+tab-bar-misc-info
                            +tab-bar-org-clock
                            +tab-bar-bongo
                            ;;+tab-bar-emms
                            +tab-bar-tracking-mode
                            +tab-bar-notmuch-count
                            +tab-bar-date))
  (tab-bar-mode +1)
  (display-time-mode +1))

(setup timer-list
  (:bind "d" #'timer-list-cancel)
  (:hook #'hl-line-mode
         #'lin-mode))

(setup tramp
  (el-patch-feature tramp)
  (with-eval-after-load 'tramp
    (el-patch-defun tramp-debug-buffer-command-completion-p (_symbol buffer)
      "A predicate for Tramp interactive commands.
 They are completed by \"M-x TAB\" only in Tramp debug buffers."
      (with-current-buffer buffer
        (el-patch-wrap 2
          (save-restriction
            (widen)
            (string-equal (buffer-substring 1 10) ";; Emacs:")))))))

(setup (:straight 0x0)
  (:option 0x0-default-server 'ttm)
  (with-eval-after-load 'embark
    (define-key embark-region-map (kbd "U") #'0x0-dwim)))

(setup (:straight ace-window)
  (:require +ace-window)
  (:option aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)
           aw-display-mode-overlay nil
           aw-scope 'frame
           aw-minibuffer-flag t)
  (:+key "M-o" #'+ace-window-or-switch-buffer)
  (:face 'aw-mode-line-face '((t (:foreground "red"))))
  (+ace-window-display-mode +1))

(setup (:straight (actually-selected-window
                   :host github
                   :repo "duckwork/actually-selected-window.el"))
  (actually-selected-window-mode +1))

(setup (:straight adaptive-wrap)
  (:with-mode adaptive-wrap-prefix-mode
    (:hook-into visual-column-mode)))

(setup (:straight affe
         (or (executable-find "rg")
             (and (executable-find "find")
                  (executable-find "grep"))))
  (:load-after consult orderless vertico)
  (setq affe-regexp-compiler (defun affe-orderless-regexp-compiler (input &rest _)
                               (setq input (orderless-pattern-compiler input))
                               (cons input (lambda (str) (orderless--highlight input str)))))
  (+with-eval-after-loads (affe vertico-multiform)
    (setq affe-regexp-compiler (defun affe-orderless-regexp-compiler (input &rest _)
                                 (setq input (orderless-pattern-compiler input))
                                 (cons input (lambda (str) (orderless--highlight input str)))))
    (setf (alist-get 'affe-grep vertico-multiform-commands) nil
          (alist-get 'affe-find vertico-multiform-commands) nil)
    (:+key "M-s g" #'affe-grep
           "M-s f" #'affe-find)))

(setup (:straight alert)
  (:option alert-default-style 'libnotify))

(setup (:straight anzu)
  (:option anzu-cons-mode-line-p nil)
  (:+key [remap query-replace] #'anzu-query-replace-regexp
         [remap query-replace-regexp] #'anzu-query-replace-regexp)
  (global-anzu-mode +1)
  (:bind-into isearch
    [remap isearch-query-replace] #'anzu-isearch-query-replace
    [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace-regexp))

(setup (:straight avy)
  (:require avy +avy)
  (:option avy-background t
           avy-lead-faces
           '(avy-lead-face
             avy-lead-face-1 avy-lead-face-1 avy-lead-face-1
             avy-lead-face-1 avy-lead-face-1 avy-lead-face-1))
  (:face 'avy-background-face
         '((t (:foreground "#888888"))))
  (:+key "M-j" #'avy-goto-char-timer)
  (:bind-into isearch
    "M-j" #'avy-isearch)
  (:when-loaded
    (setf (alist-get ?. avy-dispatch-alist) #'avy-action-embark)))

(setup (:straight bbdb)
  (:straight bbdb-vcard)
  (:require bbdb-autoloads
            bbdb)
  (bbdb-initialize 'gnus 'message))

(setup (:straight (bongo :type git
                         :flavor melpa
                         :files ("*.el" "*.texi" "images" "*.rb" "bongo-pkg.el" "*.info")
                         :pre-build ("makeinfo" "--no-split" "bongo.texi")
                         :host github
                         :repo "dbrock/bongo"))
  (:also-load +bongo)
  (:option bongo-default-directory "~/var/music"
           bongo-custom-backend-matchers '((mpv . (("https:") . t)))
           +bongo-radio-stations        ; use `+bongo-radio' for these
           `(;; Local radio
             ("KLSU"
              . "http://130.39.238.143:8010/stream.mp3")
             ("WRKF: NPR for the Capital Region"
              . ,(concat "https://playerservices.streamtheworld.com/api/"
                         "livestream-redirect/WRKFFM.mp3"))
             ("WRKF HD-2"
              . ,(concat "https://playerservices.streamtheworld.com/api/"
                         "livestream-redirect/WRKFHD2.mp3"))
             ("WBRH: Jazz & More"
              . "http://wbrh.streamguys1.com/wbrh-mp3")
             ("KBRH Blues & Rhythm Hits"
              . "http://wbrh.streamguys1.com/kbrh-mp3")
             ;; Soma FM
             ("Soma FM Synphaera"
              . "https://somafm.com/synphaera256.pls")
             ("SomaFM BAGel Radio"
              . "https://somafm.com/bagel.pls")
             ("SomaFM Boot Liquor"
              . "https://somafm.com/bootliquor320.pls")
             ("SomaFM Deep Space One"
              . "https://somafm.com/deepspaceone.pls")
             ("SomaFM Fluid"
              . "https://somafm.com/fluid.pls")
             ("SomaFM Underground 80s"
              . "https://somafm.com/u80s256.pls")
             ;; Tildeverse & Friends
             ("tilderadio"
              . "https://azuracast.tilderadio.org/radio/8000/radio.ogg")
             ("vantaradio"
              . "https://vantaa.black/radio")
             ;; Other online radio
             ("BadRadio: 24/7 PHONK"
              . "https://s2.radio.co/s2b2b68744/listen")
             ("Cafe - lainon.life"
              . "https://lainon.life/radio/cafe.ogg.m3u")
             ("Everything - lainon.life"
              . "https://lainon.life/radio/everything.ogg.m3u")
             ("Swing - lainon.life"
              . "https://lainon.life/radio/swing.ogg.m3u")
             ("Cyberia - lainon.life"
              . "https://lainon.life/radio/cyberia.ogg.m3u")
             ("Nightwave Plaza - Online Vaporwave Radio"
              . "http://radio.plaza.one/opus")))
  (advice-add 'bongo-play :before #'+bongo-stop-all)
  (with-eval-after-load 'notifications
    (add-hook 'bongo-player-metadata-changed-hook #'+bongo-notify)))

(setup (:straight browse-kill-ring)
  (:+key "C-M-y" #'browse-kill-ring)
  (:option browse-kill-ring-highlight-current-entry t
           browse-kill-ring-highlight-inserted-item 'pulse
           browse-kill-ring-separator "")
  (:hook #'form-feed-mode))

(setup (:straight (cape
                   :host github :repo "minad/cape"))
  (dolist (fn
           ;; All available cape capfs listed here. Add them to the front since
           ;; they're reversed with `add-to-list'.
           '(cape-file
             cape-dabbrev
             cape-keyword
             ;;cape-abbrev
             cape-ispell
             ;;cape-dict
             ))
    (add-to-list 'completion-at-point-functions fn :append)))

(setup (:straight circe)
  (:require _circe
            +circe)
  (:also-load circe-chanop)
  (+ensure-after-init (lambda () (defalias 'irc '+irc "Start IRC.")))

  ;; Formatting options
  (:option
   ;; Messages between users
   circe-format-action (format (format "%%%ds* {nick} {body}"
                                       (- +circe-left-margin 2))
                               " ")
   circe-format-say (format "{nick:%1$d.%1$ds} | {body}"
                            (- +circe-left-margin 3))
   circe-format-self-action circe-format-action
   circe-format-self-say (replace-regexp-in-string "|" ">" circe-format-say)
   circe-format-notice (format "-{nick:%1$d.%1$ds}---{body}"
                               (- +circe-left-margin 4))
   circe-format-message (format (format "%%%ds@ *{nick}* {body}"
                                        (- +circe-left-margin 2))
                                " ")
   circe-format-message-action (replace-regexp-in-string "@" "*"
                                                         circe-format-message)
   circe-format-self-message (format (format "%%%ds> *{chattarget}* {body}"
                                             (- +circe-left-margin 2))
                                     " ")
   ;; Meta messages
   circe-format-server-channel-creation-time (+circe-format-meta
                                              (concat "Channel {channel}"
                                                      " created on {date}") t)
   circe-format-server-ctcp (+circe-format-meta
                             (concat "CTCP PING request to {target} from"
                                     " {userhost}: {body}"))
   circe-format-server-ctcp-ping-reply (+circe-format-meta
                                        (concat
                                         "CTCP PING reply to {target} from"
                                         " {userhost}: {body}"))
   circe-format-server-part (+circe-format-meta "PART {channel}: {reason}")
   circe-format-server-quit (+circe-format-meta "QUIT: {reason}")
   circe-format-server-quit-channel (+circe-format-meta
                                     "QUIT {channel}: {reason}")
   circe-format-server-join (+circe-format-meta "JOIN: {userinfo}")
   circe-format-server-join-in-channel (+circe-format-meta
                                        "JOIN {channel}: {userinfo}")
   circe-format-server-lurker-activity (+circe-format-meta
                                        "(JOINED {joindelta} ago)")
   circe-format-server-message (+circe-format-meta "{body}" t)
   circe-fromat-server-mode-change (+circe-format-meta
                                    (concat "MODE: {target} {change}"
                                            " by {setter} ({userhost})") t)
   circe-format-server-netmerge (+circe-format-meta
                                 (concat "NETMERGE: {split} at {date}"
                                         " (/WL to see who's still missing)") t)
   circe-format-server-netsplit (+circe-format-meta
                                 (concat "NETSPLIT: {split}"
                                         " (/WL to see who left)") t)
   circe-format-server-nick-change (+circe-format-meta
                                    "NICK WAS {old-nick} ({userhost})"
                                    "new-nick")
   circe-format-server-nick-regain (+circe-format-meta
                                    "NICK REGAINED: {old-nick} ({userhost})"
                                    "new-nick")
   circe-format-server-notice (+circe-format-meta "-SERVER NOTICE- {body}" t)
   circe-format-server-topic-time (+circe-format-meta
                                   "TOPIC SET BY {setter} on {topic-date}")
   circe-format-server-topic-time-for-channel (+circe-format-meta
                                               (concat
                                                "TOPIC ({channel}) SET BY"
                                                " {setter} on {topic-date}"))
   circe-format-server-whois-idle (+circe-format-meta "IDLE FOR {idle-duration}"
                                                      "whois-nick")
   circe-format-server-whois-idle-with-signon (+circe-format-meta
                                               (concat
                                                "IDLE FOR {idle-duration}"
                                                " (signon: {signon-date})")
                                               "whois-nick")
   circe-format-server-rejoin (+circe-format-meta
                               (concat "REJOIN: {userinfo} "
                                       "after {departuredelta}"))
   circe-format-server-topic (+circe-format-meta "TOPIC: {new-topic}")
   circe-prompt-string (format (format "%%%ds> "
                                       (- +circe-left-margin 2))
                               " "))

  (:option +circe-server-buffer-action (lambda (buf)
                                         (message "Connected to %s" buf))
           +circe-network-inhibit-autoconnect _circe-network-inhibit-autoconnect
           circe-network-options _circe-network-options
           circe-color-nicks-everywhere t
           circe-default-part-message "See You, Space Cowpokes . . ."
           circe-default-user user-real-login-name
           circe-reduce-lurker-spam t
           circe-server-auto-join-default-type :after-auth)
  (:bind "C-c C-p" #'circe-command-PART
         "C-c C-t" #'+circe-current-topic
         "C-l" #'lui-track-jump-to-indicator
         "C-<return>" #'+circe-chat@set-prompt)

  ;; XXX: this doesn't quite work right.
  (advice-add #'circe-command-PART :after #'+circe-kill-buffer)
  (advice-add #'circe-command-QUIT :after #'+circe-quit@kill-buffer)
  (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
           ;; Filters
           ;;#'+circe-F/C-mode
           ;; For some reason `+circe-shorten-url-mode' won't work right out of
           ;; the gate.
           ;;(lambda () (run-at-time 0.25 nil #'+circe-shorten-url-mode))
           )
    (:bind "C-c C-s" #'circe-command-SLAP))

  (:with-mode lui-mode
    (:option lui-fill-column (+ fill-column +circe-left-margin)
             lui-fill-type nil
             lui-time-stamp-position 'right-margin
             lui-time-stamp-format "| %H:%M"
             lui-track-behavior 'before-switch-to-buffer
             lui-track-indicator 'bar
             lui-fill-remove-face-from-newline nil
             lui-formatting-list `((,(+lui-make-formatting-list-rx "*")
                                    1 lui-strong-face)
                                   (,(+lui-make-formatting-list-rx "_")
                                    1 lui-emphasis-face)
                                   (,(+lui-make-formatting-list-rx "/")
                                    1 lui-emphasis-face))
             lui-autopaste-function
             (defun +0x0-upload-string (string)
               "Upload a string using 0x0."
               (with-temp-buffer
                 (insert string)
                 (0x0-upload-text (0x0--choose-server)))
               (current-kill 0)))
    (add-to-list '+pulse-location-commands #'lui-track-jump-to-indicator)
    (:face 'lui-track-bar '((t ( :height 10
                                 :underline ( :color foreground-color
                                              :style line
                                              :position line)
                                 :extend t :inhert (default)))))
    (:hook #'visual-line-mode
           #'enable-lui-track
           #'visual-fill-column-mode
           (defun +disable-electric-pair-mode ()
             "Disable `electric-pair-mode' in the current buffer."
             (interactive)
             (electric-pair-local-mode -1))
           #'enable-lui-autopaste)
    (:local-set fringes-outside-margins t
                right-margin-width (length lui-time-stamp-format)
                scroll-margin 0
                scroll-step 1
                word-wrap t
                wrap-prefix (+string-repeat +circe-left-margin " ")
                line-number-mode nil
                column-number-mode nil
                file-percentage-mode nil
                visual-fill-column-extra-text-width
                (cons +circe-left-margin 0))
    (with-eval-after-load 'vertico-multiform
      (setf (alist-get 'lui-next-button-or-complete vertico-multiform-commands)
            '(flat))))

  (:with-mode tracking-mode
    (:option tracking-position 'before-modes)
    (:bind "C-c C-SPC" (lambda () (interactive)
                         (if (and +tracking-hide-when-org-clocking
                                  (fboundp 'org-clocking-p)
                                  (org-clocking-p))
                             (message "Bro, get back to work!")
                           (call-interactively #'tracking-next-buffer))))
    (add-to-list 'mode-line-misc-info
                 '(tracking-mode
                   tracking-mode-line-buffers)))

  (with-eval-after-load 'topsy
    (:option (append topsy-mode-functions)
             '(circe-channel-mode . +circe-current-topic)))

  (with-eval-after-load 'circe-color-nicks
    (add-hook 'modus-themes-after-load-theme-hook #'circe-nick-color-reset))
  (add-hook 'kill-emacs-hook #'+circe-quit-all@kill-emacs))

(setup (:straight (clean-kill-ring
                   :host github
                   :repo "NicholasBHubbard/clean-kill-ring.el"))
  (:require)
  (:option clean-kill-ring-prevent-duplicates t)
  (clean-kill-ring-mode +1))

(setup (:straight clhs))

(setup (:straight consult)
  (:require consult +consult)
  ;; from Consult wiki
  (:option register-preview-delay 0
           register-preview-function #'consult-register-format
           xref-show-xrefs-function #'consult-xref
           xref-show-definitions-function #'consult-xref
           tab-always-indent 'complete
           completion-in-region-function #'consult-completion-in-region)
  (advice-add #'register-preview :override #'consult-register-window)
  (advice-add #'completing-read-multiple :override
              #'consult-completing-read-multiple)
  (dolist (binding '(;; C-c bindings (mode-specific-map)
                     ("C-c h" . consult-history)
                     ("C-c m" . consult-mode-command)
                     ("C-c b" . consult-bookmark)
                     ("C-c k" . consult-kmacro)
                     ;; C-x bindings (ctl-x-map)
                     ("C-x M-:" . consult-complex-command)
                     ("<f2>" . consult-buffer)
                     ("C-x b" . consult-buffer)
                     ("C-x 4 b" . consult-buffer-other-window)
                     ("C-x 5 b" . consult-buffer-other-frame)
                     ;; Custom M-# bindings for fast register access
                     ("M-#" . consult-register-load)
                     ("M-'" . consult-register-store)
                     ("C-M-#" . consult-register)
                     ;; Other custom bindings
                     ("M-y" . consult-yank-pop)
                     ;;("<f1> a" . consult-apropos)
                     ;; M-g bindings (goto-map)
                     ("M-g e" . consult-compile-error)
                     ("M-g f" . consult-flymake) ; or consult-flycheck
                     ("M-g g" . consult-goto-line)
                     ("M-g M-g" . consult-goto-line)
                     ("M-g o" . consult-outline) ; or consult-org-heading
                     ("M-g m" . consult-mark)
                     ("M-g k" . consult-global-mark)
                     ("M-g i" . consult-imenu)
                     ("M-g M-i" . consult-imenu)
                     ("M-g I" . consult-imenu-multi)
                     ;; M-s bindings (search-map)
                     ("M-s f" . consult-find)
                     ("M-s F" . consult-locate)
                     ("M-s g" . consult-grep)
                     ("M-s G" . consult-git-grep)
                     ("M-s r" . consult-ripgrep)
                     ("M-s l" . consult-line)
                     ("M-s L" . consult-line-multi)
                     ("M-s m" . consult-multi-occur)
                     ("M-s k" . consult-keep-lines)
                     ("M-s u" . consult-focus-lines)
                     ;; Isearch integration
                     ("M-s e" . consult-isearch-history)))
    (global-set-key (kbd (car binding)) (cdr binding)))
  (with-eval-after-load 'isearch-mode
    (dolist (binding '(("M-e" . consult-isearch-history)
                       ("M-s e" . consult-isearch-history)
                       ("M-s l" . consult-line)
                       ("M-s L" . consult-line-multi)))
      (define-key isearch-mode-map (car binding) (cdr binding))))
  (:+menu "b" #'consult-buffer
          "f" #'find-file)
  (:bind-into org
    "M-g o" #'consult-org-heading)
  (advice-add 'consult-yank-pop :after #'+yank@indent)
  (:when-loaded
    (:option consult-narrow-key "<"
             consult-project-root-function '+consult-project-root)
    (consult-customize consult-theme
                       :preview-key '(:debounce 0.2 any))
    (consult-customize consult-ripgrep consult-git-grep consult-grep
                       consult-bookmark consult-recent-file consult-xref
                       consult--source-recent-file
                       consult--source-project-recent-file
                       consult--source-bookmark consult-buffer
                       :preview-key (kbd "M-,"))
    (consult-history-to-modes ((minibuffer-local-map . nil)
                               (shell-mode-map . shell-mode-hook)
                               (term-mode-map . term-mode-hook)
                               (term-raw-map . term-mode-hook)
                               (comint-mode-map . comint-mode-hook)
                               (sly-mrepl-mode-map . sly-mrepl-hook)))
    (with-eval-after-load 'orderless
      (:option consult--regexp-compiler #'consult--orderless-regexp-compiler))
    (with-eval-after-load 'vertico-multiform
      (setf (alist-get 'consult-buffer vertico-multiform-commands) '(flat))
      (dolist (buf-cmd '(consult-find
                         consult-yank-pop
                         consult-locate
                         consult-grep
                         consult-git-grep
                         consult-ripgrep
                         consult-line
                         consult-line-multi
                         consult-multi-occur
                         consult-keep-lines
                         consult-focus-lines
                         consult-imenu
                         consult-imenu-multi
                         consult-outline))
        (setf (alist-get buf-cmd vertico-multiform-commands) nil)))))

(setup (:straight consult-dir)
  (:+key "C-x C-d" #'consult-dir)
  (:with-map vertico-map
    (:bind "C-x C-d" #'consult-dir
           "C-x C-j" #'consult-dir-jump-file)))

(setup (:straight consult-notmuch)
  (:load-after consult notmuch)
  (with-eval-after-load 'vertico-multiform
    (setf (alist-get 'consult-notmuch vertico-multiform-commands) nil
          (alist-get 'consult-notmuch-tree vertico-multiform-commands) nil)))

(setup (:straight corfu
         :quit "Turns out, I actually like minibuffer completion better.")
  (+with-ensure-after-init
    (corfu-global-mode +1)))

(setup (:straight crossword)
  ;; This isn't the perfect Emacs crossword puzzle, but it's the only one I
  ;; know.
  (:hook #'turn-off-+key-mode)
  (:option crossword-save-path (sync/ "emacs/crosswords/" t)
           crossword-empty-position-char "=")
  (:face 'crossword-grid-face '((t :inherit 'font-lock-string-face))
         'crossword-current-face '((t :inherit 'highlight))
         'crossword-other-dir-face '((t :inherit 'font-lock-keyword-face))))

(setup (:straight crux)
  ;; yes it's silly I have an addon to this addon.
  (:require crux +crux)
  (:option crux-shell-func #'crux-eshell
           crux-shell-buffer-name "eshell"
           +crux-default-date-format "%F")
  (:global "C-o" #'crux-smart-open-line
           "C-x 4 t" #'crux-transpose-windows
           "M-w" #'+crux-kill-ring-save
           "C-k" #'+crux-kill-and-join-forward
           "C-c d" #'+crux-insert-date-or-time)
  (crux-with-region-or-buffer indent-region)

  (el-patch-feature crux)
  (with-eval-after-load 'crux
    (el-patch-defun crux-reopen-as-root ()
      "Find file as root if necessary.

 Meant to be used as `find-file-hook'.
 See also `crux-reopen-as-root-mode'."
      (unless (or
               ;; This helps fix for `nov-mode', and possibly others.
               (el-patch-add (null buffer-file-name))
               (tramp-tramp-file-p buffer-file-name)
               (equal major-mode 'dired-mode)
               (not (file-exists-p (file-name-directory buffer-file-name)))
               (file-writable-p buffer-file-name)
               (crux-file-owned-by-user-p buffer-file-name))
        (crux-find-alternate-file-as-root buffer-file-name))))
  (crux-reopen-as-root-mode +1))

(setup (:straight csv-mode))

(setup (:straight dictionary)
  (:option dictionary-use-single-buffer t)
  (autoload 'dictionary-search "dictionary"
    "Ask for a word and search it in all dictionaries" t)
  (:hook #'reading-mode))

(setup (:straight diff-hl)
  (global-diff-hl-mode +1))

(setup (:straight dired-git-info)
  (:bind-into dired
    ")" #'dired-git-info-mode))

(setup (:straight dired-open)
  (:load-after dired))

(setup (:straight dired-subtree)
  (:load-after dired)
  (:bind-into dired
    "TAB" #'dired-subtree-cycle
    "i" #'dired-subtree-toggle))

(setup (:straight (discord
                   :host github
                   :repo "davep/discord.el"
                   :fork (:repo "duckwork/discord.el"))))

(setup (:straight dumb-jump)
  (add-hook 'xref-backend-functions #'dumb-jump-xref-activate))

(setup (:straight ebuku
         (executable-find "buku"))
  (:option ebuku-display-on-startup 'recent
           ebuku-recent-count 100))

(setup (:straight edit-server)
  (:option edit-server-url-major-mode-alist `(("github\\.com" . ,(if (fboundp 'gfm-mode)
                                                                     #'gfm-mode
                                                                   #'markdown-mode))))
  (add-hook 'edit-server-done-hook (lambda () (unfill-region (point-min) (point-max))))
  (+with-ensure-after-init
    (edit-server-start)))

(setup (:straight electric-cursor)
  (:option electric-cursor-alist '((overwrite-mode . hbar)
                                   (god-local-mode . box)
                                   (t . bar)))
  (electric-cursor-mode +1))

(setup (:straight elfeed)
  (:require +elfeed)
  (+define-dir elfeed/ (sync/ "emacs/elfeed/" t))
  (:option
   elfeed-curl-program-name (executable-find "curl")
   elfeed-use-curl elfeed-curl-program-name
   elfeed-curl-extra-arguments '("--insecure")
   elfeed-enclosure-default-dir (cl-loop for dir in '("~/var/download/"
                                                      "~/Downloads/")
                                         if (file-exists-p dir)
                                         return dir)
   elfeed-search-filter "@1-month-ago +unread"
   elfeed-search-trailing-width 24
   elfeed-search-title-min-width 24
   elfeed-search-title-max-width 78
   elfeed-search-remain-on-entry t
   elfeed-show-unique-buffers t
   elfeed-db-directory (elfeed/ "db/" t))
  (:+leader "f" #'elfeed "C-f" #'elfeed)
  (advice-add #'elfeed-search-fetch :after #'beginning-of-buffer)
  (:with-mode elfeed-search-mode
    (:bind "&" #'+elfeed-search-browse-generic
           "w" #'elfeed-search-yank
           "y" nil
           "a" #'+elfeed-show-mark-read-and-advance)
    (: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 :before #'elfeed-db-load))
  (:with-mode elfeed-show-mode
    (:bind "SPC" #'+elfeed-scroll-up-command
           "S-SPC"  #'+elfeed-scroll-down-command
           "&" #'+elfeed-show-browse-generic
           "RET" #'shr-browse-url
           "w" #'elfeed-show-yank
           "y" nil)
    (:hook #'reading-mode)
    (:option +elfeed--update-repeat (* 60 30) ; 1/2 hour
             +elfeed--update-first-time 60))
  (+elfeed-update-async-mode +1)
  (add-hook '+elfeed-update-proceed-hook (defun non-work-hours? ()
                                           "Return nil if during work hours, t otherwise."
                                           (let* ((now (current-time))
                                                  (now* (decode-time now))
                                                  (work-start* (append '(0 0 8) (cdddr now*))) ; 8:00 AM
                                                  (work-end* (append '(0 0 18) (cdddr now*)))  ; 6:00 PM
                                                  (work-start (encode-time work-start*))
                                                  (work-end (encode-time work-end*)))
                                             (or (time-less-p now work-start)
                                                 (time-less-p work-end now))))))

(setup (:straight elfeed-org)
  (:also-load +org-capture)
  (:option rmh-elfeed-org-files (list (elfeed/ "elfeed.org" t)))
  (elfeed-org)
  (+org-capture-templates-setf "f"
      `("Feed" entry
        (file+olp ,(car rmh-elfeed-org-files) "Feeds")
        "* %? %^g")))

(setup (:straight elpher)
  (:bind "l" #'elpher-back))

(setup (:straight 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
           "M-." #'embark-dwim))
  (:with-map embark-file-map
    (:bind "l" #'vlf))
  ;; Integrations
  (with-eval-after-load 'vertico-multiform
    (setf (alist-get 'embark-prefix-help-command vertico-multiform-commands)
          nil)))

(setup (:straight embark-consult)
  (:load-after consult embark)
  (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode))

(setup (:straight embrace)
  (:+key "C-," #'embrace-commander))

(setup (:straight (ement
                   :host github
                   :repo "alphapapa/ement.el")
         ;; `plz' is a requirement, but isn't on an elpa.
         (setup (:straight (plz :host github
                                :repo "alphapapa/plz.el"))
           t)))

(setup (:straight epithet)
  (add-hook 'epithet-suggesters #'epithet-for-eww-url)
  (dolist (hook '(Info-selection-hook
                  ;; eww-after-render-hook
                  help-mode-hook
                  occur-mode-hook))
    (add-hook hook #'epithet-rename-buffer))
  (if (boundp 'eww-auto-rename-buffer)  ; Emacs 29
      (:option eww-auto-rename-buffer 'title)
    (add-hook 'eww-after-render-hook #'epithet-rename-buffer)))

(setup (:straight eros)
  (:option eros-eval-result-prefix "; "
           eros-overlays-use-font-lock nil)
  (:hook-into emacs-lisp-mode
              lisp-interaction-mode))

(setup (:straight eshell-bookmark)
  (add-hook 'eshell-mode-hook #'eshell-bookmark-setup))

(setup (:straight eshell-syntax-highlighting)
  (:hook-into eshell-mode))

(setup (:straight eshell-vterm
         :quit)
  (:load-after eshell)
  (defalias 'eshell/v 'eshell-exec-visual)
  (eshell-vterm-mode +1))

(setup (:straight exec-path-from-shell
         (eq system-type 'gnu/linux))
  (require 'exec-path-from-shell)
  (dolist (var '("SSH_AUTH_SOCK"
                 "SSH_AGENT_PID"
                 "GPG_AGENT_INFO"
                 "LANG"
                 "LC_CTYPE"
                 "XDG_CONFIG_HOME"
                 "XDG_CONFIG_DIRS"
                 "XDG_DATA_HOME"
                 "XDG_DATA_DIRS"
                 "XDG_CACHE_HOME"))
    (add-to-list 'exec-path-from-shell-variables var))
  (exec-path-from-shell-initialize))

(setup (:straight expand-region)
  (:require expand-region +expand-region)
  (:option expand-region-fast-keys-enabled nil)
  (:+key "C-=" #'er/expand-region
         "C--" #'+er/contract-or-negative-argument))

(setup (:straight (fill-sentences-correctly
                   :host github
                   :repo "duckwork/fill-sentences-correctly.el"))
  (fill-sentences-correctly-mode +1))

(setup (:straight (filldent
                   :host github
                   :repo "duckwork/filldent.el"))
  (:+key "M-q" #'filldent-unfill-toggle))

(setup (:straight (flymake-collection
                   :host github
                   :repo "mohkale/flymake-collection"))
  (+ensure-after-init #'flymake-collection-hook-setup))

(setup (:straight (flyspell-correct
                   :fork (:host github :repo "duckwork/flyspell-correct"
                                :branch "metadata-category")))
  (:load-after flyspell)
  (:also-load +flyspell-correct)
  (:option flyspell-correct--cr-key ";")
  (:bind-into flyspell
    "C-;" #'flyspell-correct-wrapper
    "<f7>" #'+flyspell-correct-buffer)
  (with-eval-after-load 'vertico-multiform
    (setf (alist-get 'flyspell vertico-multiform-categories) nil)))

(setup (:straight focus)
  (:require)
  (add-hook 'modus-themes-after-load-theme-hook
            (defun focus-update@after-modus-load ()
              (modus-themes-with-colors
                (:face 'focus-unfocused `((t ( :foreground ,fg-inactive
                                               :background ,bg-inactive
                                               :weight normal
                                               :slant normal
                                               :extend t)))))))
  ;; XXX: This doesn't work, because notmuch overlays shit on the buffer
  (setf (alist-get 'notmuch-show-mode focus-mode-to-thing)
        'notmuch-message)
  (:hook-into notmuch-show-mode))

(setup (:straight (forge
                   :host github :repo "magit/forge")
         (eq system-type 'gnu/linux))
  (require 'forge)
  (add-to-list 'forge-alist
               '("tildegit.org" "tildegit.org/api/v1" "tildegit.org"
                 forge-gitea-repository)))

(setup (:straight form-feed)
  ;; See also `page-break-lines', further down.
  (:face 'form-feed-line '((t (:strike-through t))))
  (global-form-feed-mode +1))

(setup (:straight (frowny
                   :host github
                   :repo "duckwork/frowny.el"))
  (global-frowny-mode +1))

(setup (:straight gcmh)
  (:option gcmh-idle-delay 'auto)
  (gcmh-mode +1))

(setup (:straight (geiser
                   :type git
                   :flavor melpa
                   :files ("elisp/*.el" "doc/*" "geiser-pkg.el")
                   :pre-build ("make" "-Cdoc" "geiser.info")
                   :host gitlab
                   :repo "emacs-geiser/geiser"))
  (dolist (pkg '( geiser-chicken geiser-guile
                  macrostep-geiser
                  scheme-complete))
    (straight-use-package pkg))
  (:require +chicken)
  (setf (alist-get "\\.scm\\'" auto-mode-alist nil nil #'string=)
        'scheme-mode)
  (setf (alist-get "\\.scm\\'" auto-mode-alist nil nil #'string=)
        '(insert "#!/bin/sh\n#| -*- scheme -*-\nexec csi -s $0 \"$@\"\n|#\n")))

(setup (:straight (git-modes
                   :host github :repo "magit/git-modes"))
  (:require git-modes))

(setup (:straight god-mode
         :quit "I could never get the hang of this.")
  (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)
  (:+key "<f1> f" #'helpful-callable
         "<f1> v" #'helpful-variable
         "<f1> k" #'helpful-key
         "<f1> ." #'helpful-at-point)
  (with-eval-after-load 'vertico-multiform
    (dolist (cmd '(describe-symbol     ; describe-* included here for completeness
                   describe-function describe-variable
                   helpful-function helpful-macro helpful-callable
                   helpful-variable))
      (setf (alist-get cmd vertico-multiform-commands) nil)))
  ;; Load faster on first invocation by pre-loading a slow function
  ;; (see https://github.com/Wilfred/helpful/issues/236)
  (run-with-idle-timer 1 nil (lambda ()
                               (require 'info-look)
                               (info-lookup-setup-mode 'symbol 'emacs-lisp-mode))))

(setup (:straight (hippie-completing-read
                   :host github
                   :repo "duckwork/hippie-completing-read"))
  (:+key "M-/" #'hippie-completing-read))

(setup (:straight hungry-delete)
  (:option hungry-delete-chars-to-skip " \t"
           hungry-delete-join-reluctantly nil)
  (+with-ensure-after-init
    (add-to-list 'hungry-delete-except-modes 'eshell-mode))
  (:bind-into paredit
    ;; I define these functions here because they really require both packages
    ;; to make any sense.  So, would I put them in `+hungry-delete' or
    ;; `+paredit' ?  There's no satisfactory answer.
    [remap paredit-backward-delete]
    (defun acdw/paredit-hungry-delete-backward (arg)
      (interactive "P")
      (if (looking-back "[ \t]" 1)
          (hungry-delete-backward (or arg 1))
        (paredit-backward-delete arg)))
    [remap paredit-forward-delete]
    (defun acdw/paredit-hungry-delete-forward (arg)
      (interactive "P")
      (if (looking-at "[ \t]")
          (hungry-delete-forward (or arg 1))
        (paredit-forward-delete arg))))
  (global-hungry-delete-mode +1))

(setup (:straight i3wm-config-mode
         (executable-find "i3")))

(setup (:straight info+)
  (:load-after info)
  (:option Info-fontify-isolated-quote-flag nil
           Info-fontify-quotations nil
           Info-saved-history-file (.etc "info-history"))
  (add-hook 'Info-mode-hook #'Info-variable-pitch-text-mode))

(setup (:straight isearch-mb)
  ;; This complicatedness is an attempt to make it easier to add and
  ;; subtract `isearch-mb' bindings using the suggestions in the
  ;; project's README.
  (:load-after consult anzu)
  (:when-loaded
    (dolist (spec '((isearch-mb--with-buffer
                     ("M-e" .  consult-isearch)
                     ("C-o" . loccur-isearch))
                    (isearch-mb--after-exit
                     ("M-%" . anzu-isearch-query-replace)
                     ("M-s l" . consult-line))))
      (let ((isearch-mb-list (car spec))
            (isearch-mb-binds (cdr spec)))
        (dolist (cell isearch-mb-binds)
          (let ((key (car cell))
                (command (cdr cell)))
            (when (fboundp command)
              (add-to-list isearch-mb-list command)
              (define-key isearch-mb-minibuffer-map (kbd key) command)))))))
  (isearch-mb-mode +1))

(setup (:straight (jabber :repo "https://codeberg.org/emacs-jabber/emacs-jabber"
                          :host nil
                          :files ("*.el" "*.texi"
                                  ("jabber-fallback-lib"
                                   "jabber-fallback-lib/hexrgb.el"
                                   "jabber-fallback-lib/srv.el"
                                   "jabber-fallback-lib/fsm.el")
                                  "jabber-pkg.el")
                          :fork ( :host nil
                                  :repo "https://codeberg.org/acdw/emacs-jabber")))
  (:also-load +jabber)
  (defvar +jabber-ws-prefix 0 "Width to pad left side of chats.")
  (defvar +jabber-pre-prompt "\n"
    "String to show before a prompt.")
  (:option jabber-account-list '(("acdw@hmm.st"))
           jabber-groupchat-buffer-format "%n"
           jabber-chat-buffer-format "%n"
           jabber-muc-private-buffer-format "%n(%g)"
           jabber-activity-show-p #'ignore
           jabber-muc-decorate-presence-patterns
           '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$")
             ("." . jabber-muc-presence-dim))
           jabber-muc-colorize-foreign t
           jabber-chat-foreign-prompt-format (concat +jabber-pre-prompt
                                                     "[%t] %n\n"
                                                     (make-string +jabber-ws-prefix
                                                                  ?\ ))
           jabber-chat-local-prompt-format (concat +jabber-pre-prompt
                                                   "[%t] %n\n"
                                                   (make-string +jabber-ws-prefix
                                                                ?\ ))
           jabber-groupchat-prompt-format (concat +jabber-pre-prompt
                                                  "[%t] %n\n"
                                                  (make-string +jabber-ws-prefix
                                                               ?\ ))
           jabber-auto-reconnect t)
  (add-hook 'modus-themes-after-load-theme-hook
            (defun jabber-chat@after-modus-themes-load ()
              (modus-themes-with-colors
                (:face 'jabber-chat-prompt-foreign `((t (:foreground ,red)))
                       'jabber-chat-prompt-local `((t (:foreground ,blue)))
                       'jabber-chat-prompt-system `((t (:foreground ,green)))))
              (setq jabber-muc-nick-value (pcase (frame--current-backround-mode (selected-frame))
                                            ('light 0.5)
                                            ('dark 1.0)))
              (+mapc-some-buffers #'+jabber-colors-update
                                  (lambda () (derived-mode-p 'jabber-chat-mode
                                                        'jabber-roster-mode
                                                        'jabber-activity-mode
                                                        'jabber-browse-mode)))))
  (dolist (mode '(jabber-chat-mode
                  jabber-browse-mode
                  jabber-roster-mode
                  jabber-console-mode))
    (let ((hook (intern (format "%s-hook" mode))))
      (add-hook hook #'visual-fill-column-mode)))
  (add-hook 'jabber-activity-mode-hook #'tracking-mode)
  (with-eval-after-load 'tracking
    (add-to-list 'tracking-ignored-buffers "discuss@conference.soprani.ca"))
  (:with-mode jabber-chat-mode
    (:local-set +modeline-position-function (lambda ()
                                              (cond
                                               ((string-match-p "hmm@" (buffer-name))
                                                "🤔 ")))
                wrap-prefix (make-string +jabber-ws-prefix ?\ )))
  (:+leader "C-j" jabber-global-keymap)
  (advice-add 'jabber-activity-add :after #'+jabber-tracking-add)
  (advice-add 'jabber-activity-add-muc :after #'+jabber-tracking-add-muc))

(setup (:straight (keepassxc-shim
                   :host github :repo "duckwork/keepassxc-shim.el"))
  (keepassxc-shim-activate))

(setup (:straight keychain-environment
         (executable-find "keychain"))
  (keychain-refresh-environment))

(setup (:straight lacarte)
  (:+key "<f10>" #'lacarte-execute-menu-command)
  (with-eval-after-load 'vertico-multiform
    (setf (alist-get 'lacarte-execute-menu-command vertico-multiform-commands)
          '(buffer grid (vertico-sort-function . vertico-sort-length-alpha)))))

(setup (:straight (lin :host nil
                       :repo "https://git.sr.ht/~protesilaos/lin"))
  (:require)
  (lin-global-mode +1))

(setup (:straight link-hint)
  (:require +link-hint)
  (+link-hint-open-secondary-setup)
  (+link-hint-open-chrome-setup)
  (:option link-hint-avy-style 'at-full)
  (:+key "M-l" +link-hint-map)
  (:with-map +link-hint-map
    (:bind "M-l" #'+link-hint-open-link "l" #'+link-hint-open-link
           "M-o" #'+link-hint-open-secondary "o" #'+link-hint-open-secondary
           "M-m" #'link-hint-open-multiple-links "m" #'link-hint-open-multiple-links
           "M-w" #'link-hint-copy-link "w" #'link-hint-copy-link
           "M-c" #'+link-hint-open-chrome "c" #'+link-hint-open-chrome)))

(setup (:straight (machine
                   :host github
                   :repo "duckwork/machine.el"))
  (+with-ensure-after-init              ; So that they override anything here.
    ;; Emoji fonts
    (let ((ffl (font-family-list))
          (emoji-fonts '("Noto Color Emoji"
                         "Noto Emoji"
                         "Segoe UI Emoji"
                         "Apple Color Emoji"
                         "FreeSans"
                         "FreeMono"
                         "FreeSerif"
                         "Unifont"
                         "Symbola"))
          found)
      (dolist (font emoji-fonts)
        (when (member font ffl)
          (push font found)
          (set-fontset-font t 'symbol (font-spec :family font) nil :append)))
      (nreverse found))
    (machine-settings-load)))

(setup (:straight macrostep)
  (:require macrostep)
  (dolist (m '(emacs-lisp-mode-map
               lisp-interaction-mode-map))
    (define-key (symbol-value m) (kbd "C-c e") #'macrostep-expand)))

(setup (:straight (magit :host github :repo "magit/magit")))

(setup (:straight marginalia)
  (marginalia-mode +1))

(setup (:straight markdown-mode)
  (:option markdown-hide-markup nil)
  (add-to-list 'auto-mode-alist (cons (rx (or ".md" ".markdown" ".mdown")
                                          eos)
                                      'markdown-mode))
  (with-eval-after-load 'visual-fill-column
    (:hook #'visual-fill-column-mode))
  (with-eval-after-load 'apheleia
    (when-let ((mdfmt-exe (executable-find "markdownfmt")))
      (setf (alist-get 'markdownfmt apheleia-formatters) mdfmt-exe)
      (setf (alist-get 'markdown-mode apheleia-mode-alist) 'markdownfmt)
      (setf (alist-get 'gfm-mode apheleia-mode-alist) 'markdownfmt))))

(setup (:straight mastodon)
  (:option mastodon-instance-url "https://tiny.tilde.website"
           mastodon-client--token-file (.etc "mastodon.plstore")
           mastodon-auth-source-file (seq-some (lambda (i)
                                                 (when (and (stringp i)
                                                            (file-exists-p i))
                                                   i))
                                               auth-sources)
           mastodon-tl--show-avatars t
           mastodon-tl--enable-proportional-fonts nil)
  (:hook #'mastodon-async-mode
         #'visual-fill-column-mode
         #'variable-pitch-mode
         #'hl-line-mode
         #'lin-mode))

(setup (:straight md4rd
         :quit)
  ;; `md4rd' is ... a bit janky, tbh.  But I'm including this here so I have it.
  ;; TODO: enable opening Reddit links in md4rd
  (:also-load _md4rd)
  (defalias 'reddit 'md4rd "Browse Reddit.")
  (with-eval-after-load 'md4rd
    (run-with-timer 0 (* 60 59) 'md4rd-refresh-login)))

(setup (:straight minions)
  (minions-mode +1))

(setup (:straight (mode-line-bell
                   :host github :repo "purcell/mode-line-bell"
                   :fork (:host github :repo "duckwork/mode-line-bell"
                                :branch "remap-face")))
  ;; This is still, annoyingly, not quite working right.
  (:face 'mode-line-bell '((t (:inherit mode-line-highlight))))
  (:option mode-line-bell-flash-time 0.1)
  (mode-line-bell-mode +1))

(setup (:straight (modus-themes
                   :host nil
                   :repo "https://git.sr.ht/~protesilaos/modus-themes"))
  (require 'modus-themes (.etc "straight/build/modus-themes/modus-themes"))
  (:option modus-themes-mixed-fonts t
           modus-themes-bold-constructs t
           modus-themes-italic-constructs t
           modus-themes-headings '((t t)))
  (dotimes (facen-1 8)
    (let ((facen (1+ facen-1)))
      (custom-set-faces
       `(,(intern (format "org-level-%s" facen))
         ((t :inherit
             (,(intern (format "modus-themes-heading-%s" facen))
              fixed-pitch))
          :now)))))
  (:face 'modus-themes-tab-active '((t :bold nil))
         'modus-themes-tab-inactive '((t :italic t)))

  (define-advice modus-themes--current-theme (:around (fn &rest r))
    "Fix a \"nil is not a Modus theme\" error."
    (or (apply fn r)
        'modus-operandi))

  ;; This needs to be after the themes are loaded, I think.
  (add-hook 'modus-themes-after-load-theme-hook
            (defun +modus-themes-mostly-monochrome ()
              "Set up mdous-themes to be mostly monochrome."
              (modus-themes-with-colors
                (custom-set-faces
                 `(font-lock-builtin-face ((,class :inherit modus-themes-bold
                                                   :foreground unspecified)))
                 `(font-lock-comment-delimiter-face ((,class :inherit font-lock-comment-face)))
                 `(font-lock-comment-face ((,class :inherit variable-pitch
                                                   :slant italic
                                                   :foreground ,fg-docstring)))
                 `(font-lock-constant-face ((,class :inherit underline
                                                    :foreground unspecified)))
                 `(font-lock-doc-face ((,class :inherit modus-themes-slant
                                               :foreground ,fg-docstring)))
                 `(font-lock-function-name-face ((,class :foreground unspecified)))
                 `(font-lock-keyword-face ((,class :inherit modus-themes-bold
                                                   :foreground unspecified)))
                 `(font-lock-negation-char-face ((,class :inherit modus-themes-bold
                                                         :foreground unspecified)))
                 `(font-lock-preprocessor-face ((,class :foreground unspecified)))
                 `(font-lock-regexp-grouping-backslash ((,class :foreground ,fg-escape-char-backslash)))
                 `(font-lock-regexp-grouping-construct ((,class :foreground ,fg-escape-char-construct)))
                 `(font-lock-string-face ((,class :inherit modus-themes-slant
                                                  :foreground unspecified)))
                 `(font-lock-type-face ((,class :inherit modus-themes-bold
                                                :foreground unspecified)))
                 `(font-lock-variable-name-face ((,class :foreground unspecified)))
                 `(font-lock-warning-face ((,class :inherit modus-themes-bold
                                                   :foreground ,red-nuanced-fg)))
                 `(font-lock-todo-face ((,class :inherit font-lock-comment-face
                                                :foreground ,fg-header
                                                :background ,yellow-intense-bg)))))))

  (require 'dawn)
  (dawn-schedule #'modus-themes-load-operandi
                 #'modus-themes-load-vivendi))

(setup (:straight mwim)
  (:require +mwim)
  (:option +mwim-passthrough-modes '(comint-mode
                                     eshell-mode
                                     vterm-mode
                                     crossword-mode
                                     geiser-repl-mode))
  (:global "C-a" #'mwim-beginning
           "C-e" #'mwim-end))

(setup (:straight notmuch-bookmarks)
  (:load-after notmuch)
  (:when-loaded
    (notmuch-bookmarks-mode +1)))

(setup (:straight notmuch-labeler
         :quit "Buggy")
  (:load-after notmuch))

(setup (:straight nov)
  (:hook #'visual-fill-column-mode)
  (:file-match (rx ".epub" eos)))

(setup (:straight ol-notmuch))

(setup (:straight orderless)
  (:require +orderless)
  (:option completion-styles '(substring orderless basic)
           completion-category-defaults nil
           completion-category-overrides
           '((file (styles partial-completion))
             (command (styles +orderless-with-initialism))
             (variable (styles +orderless-with-initialism))
             (symbol (styles +orderless-with-initialism)))
           orderless-component-separator #'orderless-escapable-split-on-space
           orderless-style-dispatchers '(+orderless-dispatch)))

(setup (:straight org-appear)
  (:option org-appear-autoemphasis t
           org-appear-autoentities t
           org-appear-autokeywords t
           org-appear-autolinks nil
           org-appear-autosubmarkers t
           org-appear-delay 0)
  (:hook-into org-mode))

(setup (:straight org-download)
  (:require)
  (:option org-download-method 'attach
           org-download-backend (cond ((executable-find "curl") 'curl)
                                      ((executable-find "wget") 'wget)
                                      (:else 'url-retrieve)))
  (add-hook 'dired-mode-hook 'org-download-enable))

(setup (:straight (org-drawer-list
                   :host github
                   :repo "d12frosted/org-drawer-list"))
  (:load-after org)
  (:also-load +org-drawer-list))

(setup (:straight org-mime)
  (:option org-mime-export-ascii 'utf-8)
  (add-hook 'message-mode-hook
            (defun org-mime-setup@message-mode ()
              (local-set-key (kbd "C-c M-o") 'org-mime-htmlize)))
  (add-hook 'org-mode-hook
            (defun org-mime-setup@org-mode ()
              (local-set-key (kbd "C-c M-o") 'org-mime-org-buffer-htmlize))))

(setup (:straight org-modern)
  (:option org-modern-hide-stars nil
           org-modern-star nil
           org-modern-list nil
           org-modern-progress ["..." "o.." "oo." "Oo." "Ooo" "OOo" "OOO"])
  (:face 'org-modern-label '((t ( :height 1.0
                                  :weight regular
                                  :underline nil
                                  :inherit fixed-pitch))))
  (advice-add 'org-modern--update-label-face :override #'ignore)
  (:hook-into org-mode))

(setup (:straight (org-taskwise
                   :host github
                   :repo "duckwork/org-taskwise.el"))
  (with-eval-after-load 'org
    (require 'org-taskwise)
    (define-key org-mode-map (kbd "C-x n t") #'org-taskwise-narrow-to-task)))

(setup (:straight org-visibility)
  (:require org-visibility)
  (:option org-visibility-state-file (.etc "org-visibility")
           org-visibility-include-regexps '("\\.org\\'"))
  (org-visibility-enable-hooks))

(setup (:straight orglink)
  (global-orglink-mode +1))

(setup (:straight package-lint))

(setup (:straight package-lint-flymake)
  (add-hook 'emacs-mode-hook #'package-lint-flymake-setup)
  ;; Remove it from init.el files
  (add-hook '+init-mode-hook #'flymake-mode-off))

(setup (:straight page-break-lines)
  (:option page-break-lines-char ?—)
  (:hook-into jabber-chat-mode))

(setup (:straight paredit)
  (:also-load +paredit)
  (:bind "DEL" #'paredit-backward-delete
         "C-<backspace>" #'+paredit-backward-kill-word
         "C-w" (lambda (arg) (interactive "P")
                 (+kill-word-backward-or-region arg
                                                #'paredit-backward-kill-word))
         "M-s" nil)
  (dolist (hook '(emacs-lisp-mode-hook
                  eval-expression-minibuffer-setup-hook
                  ielm-mode-hook
                  lisp-interaction-mode-hook
                  lisp-mode-hook
                  scheme-mode-hook
                  geiser-mode-hook
                  geiser-repl-mode-hook))
    (add-hook hook #'enable-paredit-mode))
  (:also-load eldoc)
  (eldoc-add-command #'paredit-backward-delete #'paredit-close-round))

(setup (:straight paren-face)
  (:hook-into emacs-lisp-mode
              ielm-mode sly-repl-mode
              lisp-mode
              lisp-interaction-mode
              scheme-mode))

(setup (:straight pdf-tools
         (or (executable-find "gcc")
             (executable-find "g++")))
  (setf (alist-get "\\.pdf\\'" auto-mode-alist nil nil #'equal)
        #'pdf-view-mode)
  (pdf-tools-install))

(setup (:straight (plancat
                   :host github
                   :repo "duckwork/plancat.el"
                   :local-repo "~/src/emacs-packages/plancat.el/"))
  (:option plancat-user "acdw"))

(setup (:straight pocket-reader)
  (:option pocket-reader-open-url-default-function #'browse-url)
  (:+leader "p" #'pocket-reader
            "C-p" #'pocket-reader)
  (dolist (mode '((eww-mode-map . eww)
                  (w3m-mode-map . w3m)
                  (elfeed-search-mode-map . elfeed-search)
                  (elfeed-show-mode-map . elfeed-show)))
    (with-eval-after-load (cdr mode)
      (define-key (symbol-value (car mode)) "\"" #'pocket-reader-add-link))
    (with-eval-after-load '+link-hint
      (+link-hint-pocket-add-setup)
      (define-key +link-hint-map "M-\"" #'+link-hint-pocket-add)
      (define-key +link-hint-map "\"" #'+link-hint-pocket-add))))

(setup (:straight rainbow-mode)
  (:hook-into prog-mode))

(setup (:straight restart-emacs))

(setup (:straight (shell-command+
                   :host nil
                   :repo "https://git.sr.ht/~pkal/shell-command-plus"))
  (:option shell-command-prompt "$ ")
  (:bind-into dired
    "M-!" 'shell-command+)
  (:+key "M-!" #'shell-command+))

(setup (:straight sicp))

(setup (:straight (simple-modeline
                   :host github :repo "gexplorer/simple-modeline"
                   :fork (:host github :repo "duckwork/simple-modeline")))
  (:require +modeline)
  (:option +modeline-modified-icon-alist '((ephemeral . "~")
                                           (special . "*")
                                           (readonly . "=")
                                           (modified . "+")
                                           (t . "-"))
           +modeline-minions-icon ";"
           +modeline-buffer-name-max-length 0.35)
  ;; Segments
  (:option simple-modeline-segments
           `((                          ; left
              +modeline-ace-window-display
              +modeline-modified
              +modeline-buffer-name
              (lambda () (+modeline-vc " : "))
              ,(+modeline-concat
                '(+modeline-minions
                  +modeline-major-mode))
              +modeline-anzu
              )
             (                          ; right
              (lambda ()
                (unless +tab-bar-misc-info-mode
                  (+modeline-concat
                   '(+modeline-track
                     simple-modeline-segment-misc-info))))
              simple-modeline-segment-process
              +modeline-text-scale
              ,(+modeline-concat
                '(+modeline-god-mode
                  +modeline-kmacro-indicator
                  +modeline-reading-mode
                  +modeline-narrowed)
                ",")
              +modeline-input-method
              +modeline-position
              )))
  (simple-modeline-mode +1))

(setup (:straight slack)
  (:also-load +slack)
  (:option slack-prefer-current-team t
           slack-buffer-emojify t
           slack-buffer-create-on-notify t
           slack-enable-wysiwyg t
           slack-file-dir (xdg-user-dir "DOWNLOAD")
           slack-display-team-name nil)
  (with-eval-after-load '+slack
    (+slack-register-teams))
  (with-eval-after-load 'alert
    ;; Don't notify for Slack messages
    (alert-add-rule :category "slack"
                    :style 'ignore)))

(setup (:straight sly
         (defvar +lisp-bin (executable-find "sbcl")))
  (:also-load sly-autoloads
              +sly)
  (:option inferior-lisp-program +lisp-bin
           sly-kill-without-query-p t)
  (:with-feature sly-mrepl
    (dolist (key '("RET" "<return>"))
      (:bind key #'sly-mrepl-return-at-end))
    (:bind "C-c C-c" #'sly-mrepl-return)))

(setup (:straight smartscan)
  (:with-map smartscan-map
    (:bind "M-'" nil))
  (:hook-into prog-mode))

(setup (:straight (sophomore
                   :host github
                   :repo "duckwork/sophomore.el"))
  (sophomore-enable #'narrow-to-region)
  (sophomore-disable                    ; These are mostly annoying commands
   #'view-hello-file
   #'describe-gnu-project)
  (sophomore-mode +1))

(setup (:straight ssh-config-mode)
  (:file-match (rx "/.ssh/config" eos)
               (rx "/ssh" (? "d") "_config" eos))
  (:with-mode ssh-known-hosts-mode
    (:file-match (rx "/knownhosts" eos)))
  (:with-mode ssh-authorized-keys-mode
    (:file-match (rx "/authorized_keys" (? "2") eos))))

(setup (:straight super-save)
  (:option auto-save-default nil
           super-save-auto-save-when-idle t
           super-save-idle-duration 60
           super-save-exclude '(".gpg")
           super-save-remote-files nil)
  (auto-save-visited-mode -1)
  (super-save-mode +1))

(setup (:straight systemd
         (executable-find "systemd"))
  (:option systemd-man-function 'woman))

(setup (:straight (titlecase
                   :host github
                   :repo "duckwork/titlecase.el"
                   :files ("*")))
  (:require titlecase)
  (:with-map +casing-map
    (:bind "t" #'titlecase-dwim
           "M-t" #'titlecase-dwim)))

(setup (:straight topsy)
  (:hook-into ;;prog-mode
   circe-chat-mode)
  (:when-loaded
    (:option
     topsy-header-line-format
     '(:eval
       (list
        (propertize " "
                    'display
                    `((space
                       :align-to
                       ,(unless (bound-and-true-p visual-fill-column-mode)
                          0))))
        (funcall topsy-fn))))))

(setup (:straight transpose-frame)
  (defvar +transpose-frame-map
    (let ((map (make-sparse-keymap)))
      (dolist (bind '(("t" . transpose-frame)
                      ("v" . flip-frame)
                      ("h" . flop-frame)
                      ("r" . rotate-frame-clockwise)
                      ("R" . rotate-frame-anticlockwise)))
        (define-key map (car bind) (cdr bind)))
      map)
    "Map for transposing frames.")
  (define-key +key-mode-map (kbd "C-x 5 t") +transpose-frame-map))

(setup (:straight trashed)
  (:option trashed-action-confirmer #'y-or-n-p))

(setup (:straight undo-fu)
  (:option undo-fu-allow-undo-in-region t)
  (:global "C-/" #'undo-fu-only-undo
           "C-?" #'undo-fu-only-redo))

(setup (:straight undo-fu-session)
  (:option undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'"
                                                "/git-rebase-todo\\'")
           undo-fu-session-directory (.etc "undo/" t)
           undo-fu-session-compression (cond
                                        ((executable-find "gzip") 'gz)
                                        ((executable-find "bzip2") 'bz2)
                                        ((executable-find "xz") 'xz)
                                        (t nil)))
  (global-undo-fu-session-mode +1))

(setup (:straight (undo-hl
                   :host github
                   :repo "casouri/undo-hl"))
  (:require)
  (:hook-into text-mode prog-mode))

(setup (:straight unfill))

(setup (:straight valign
         :quit "Doesn't work with narrowed tables.")
  (:option valign-fancy-bar t)
  (:hook-into org-mode
              markdown-mode))

(setup (:straight (vertico
                   :host github
                   :repo "minad/vertico"
                   :files ("*" "extensions/*"
                           (:exclude ".git"))))
  (:require vertico +vertico)
  (:option resize-mini-windows 'grow-only
           vertico-count-format nil
           vertico-cycle t)
  (advice-add #'vertico-next :around #'+vertico-ding-wrap)
  (when (boundp 'native-comp-deferred-compilation-deny-list)
    (add-to-list 'native-comp-deferred-compilation-deny-list "vertico"))
  (vertico-mode +1)
  ;; Extensions
  (:also-load vertico-directory
              vertico-mouse
              vertico-unobtrusive
              vertico-multiform
              vertico-quick)
  (vertico-mouse-mode +1)
  (vertico-multiform-mode +1)
  ;; I `setf' these so they don't override the other setfs elsewhere in init.el.
  (setf (alist-get 'execute-extended-command vertico-multiform-commands) '(flat))
  (setf (alist-get 'completion-at-point vertico-multiform-commands) '(flat))
  (setf (alist-get 'indent-for-tab-command vertico-multiform-commands) '(flat))
  (setf (alist-get 'insert-char vertico-multiform-commands) nil)
  (setf (alist-get 'file vertico-multiform-categories) nil)
  (setf (alist-get 'bookmark vertico-multiform-categories) nil)
  ;; Default.  Needs to be `add-to-list' so that it appears at the end.
  (add-to-list 'vertico-multiform-categories '(t flat) :append)
  (:with-map vertico-map
    (:bind "RET" #'vertico-directory-enter
           "DEL" #'vertico-directory-delete-char
           "M-DEL" #'vertico-directory-delete-word
           "TAB" #'+vertico-widen-or-complete
           "M-j" #'vertico-quick-insert))
  (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))

(setup (:straight visual-fill-column)
  (:option visual-fill-column-center-text t
           (append reading-modes) '(visual-fill-column-mode . +1))
  (:hook #'visual-line-mode)
  (:hook-into org-mode)
  (advice-add #'text-scale-adjust :after #'visual-fill-column-adjust)
  (:global [f12] #'visual-fill-column-mode))

(setup (:straight vlf)
  (:require vlf-setup))

(setup (:straight vterm
         (and module-file-suffix
              (executable-find "cmake"))
         :quit)
  (:also-load +vterm)
  (:option vterm-always-compile-module t
           vterm-buffer-name-string "vterm: %s"
           vterm-max-scrollback 100000  ; max allowed by vterm-module.h
           )
  (advice-add 'counsel-yank-pop-action :around
              #'+vterm-counsel-yank-pop-action))

(setup (:straight (vundo
                   :host github
                   :repo "casouri/vundo")))

(setup (:straight web-mode)
  (setf (alist-get (rx "." (or "htm" "html" "phtml" "tpl.php"
                               "asp" "gsp" "jsp" "ascx" "aspx"
                               "erb" "mustache" "djhtml")
                       eos)
                   auto-mode-alist)
        'web-mode)
  (with-eval-after-load 'apheleia
    (setf (alist-get 'web-mode apheleia-mode-alist)
          'prettier)))

(setup (:straight whitespace-cleanup-mode)
  (:option whitespace-cleanup-mode-preserve-point t
           whitespace-cleanup-mode-only-if-initially-clean nil)
  (global-whitespace-cleanup-mode +1))

(setup (:straight wrap-region)
  (:require wrap-region)
  (wrap-region-add-wrappers
   '(("*" "*" nil org-mode)
     ("~" "~" nil org-mode)
     ("/" "/" nil org-mode)
     ("=" "=" nil org-mode)
     ("+" "+" nil org-mode)
     ("_" "_" nil org-mode)
     ("$" "$" nil (org-mode latex-mode))))
  (:hook-into org-mode
              latex-mode))

(setup (:straight xkcd)
  (:also-load +xkcd)
  (:hook #'visual-fill-column-mode))

(setup (:straight xr))

(setup (:straight yaoddmuse))

(setup (:straight yasnippet)
  (:option yas-snippet-dirs (list
                             (expand-file-name "snippets" user-emacs-directory)
                             (sync/ "emacs/snippets" t)))
  (yas-global-mode +1))

(setup (:straight (ytdious
                   :host github :repo "spiderbit/ytdious"
                   :fork (:host github :repo "duckwork/ytdious")))
  (:also-load +ytdious)
  (:option ytdious-invidious-api-url (if +invidious-host
                                         (concat "https://" +invidious-host)
                                       "https://invidious.snopyta.org"))
  (:bind "y" #'+ytdious-watch))

(setup (:straight zoom-frm)
  (:+key "M-+" #'zoom-frm-in
         "M-_" #'zoom-frm-out))

(setup (:straight zzz-to-char)
  (:require +zzz-to-char)
  (:option zzz-to-char-reach (+bytes 1 :kib))
  (:global "M-z" #'+zzz-to-char))
