emacs/init.el

2644 lines
106 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; 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.
;;; Commentary
;; My init.el. There are many like it, but this one is mine.
;; Ideas:
;; [[https://emacs.stackexchange.com/questions/17278/truncate-only-certain-lines-and-use-continuation-lines-elsewhere][Truncate org-mode headings]]
;; [[https://emacs.stackexchange.com/questions/7432/make-visual-line-mode-more-compatible-with-org-mode][another link that might be useful for truncating]]
;;; 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 C-o" #'+switch-to-last-buffer
"C-x o" #'+switch-to-last-buffer
"C-x C-l" #'+open-paragraph ; original: downcase-region
"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)
;; 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) ; XXX: do I want this ??
(add-hook 'special-mode-hook #'turn-off-auto-fill)
;; Advice
(advice-add #'completing-read-multiple :filter-args #'+crm-indicator)
;; 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 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 find-script))
(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 #'user-save-mode-disable)))
(: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")
;;TODO: Various paste sites
))
(+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
"C-j" #'dired-up-directory)
(:hook #'dired-hide-details-mode
#'hl-line-mode
#'lin-mode
#'+dired-dim-git-ignores)
(+with-ensure-after-init ; Necessary because jabber loads later
(:+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-mode emacs-lisp-mode
(:hook #'checkdoc-minor-mode))
(:bind-into (emacs-lisp-mode-map lisp-interaction-mode-map)
"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)
(:local-set +modeline-buffer-position #'+Info-modeline-breadcrumbs
+modeline-position-function #'ignore)
(: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
(:needs "traceroute")
(:require +finger) ; fixes `finger' to use var below
(:option finger-X.500-host-regexps '(".") ; only send username
)
(require 'transient)
(transient-define-prefix net-utils ()
"Networking utilities"
["Actions"
("p" "Ping" ping)
("i" "Ifconfig" ifconfig)
("w" "Iwconfig" iwconfig)
("n" "Netstat" netstat)
("a" "Arp" arp)
("r" "Route" route)
("h" "Nslookup host" nslookup-host)
("d" "Dig" dig)
("s" "Smb Client" smbclient)
("t" "Traceroute" traceroute)])
(:+key "C-z M-n" #'net-utils))
(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"
"tag:unread"
"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)
(with-eval-after-load '+org (+org-agenda-inhibit-hooks-mode +1))
(:option org-adapt-indentation nil
org-auto-align-tags t
org-archive-mark-done t
org-fold-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 (or 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 (- (- 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
"C-c C-x l" #'org-toggle-link-display
"C-c C-x m" (lambda () (interactive)
(setq-local org-hide-emphasis-markers
(not org-hide-emphasis-markers))
(font-lock-update))
"C-c C-x r" #'+org-drawer-list-add-resource
"C-M-k" #'kill-paragraph
"C-M-t" #'transpose-paragraphs)
(:global [f8] #'org-clock-in
[f9] #'org-clock-out
"C-c l" #'org-store-link)
(:hook #'variable-pitch-mode
#'visual-fill-column-mode
#'turn-off-auto-fill
#'org-indent-mode
#'prettify-symbols-mode
#'+org-wrap-on-hyphens)
(:local-set prettify-symbols-alist '(("DEADLINE:" . ?→)
("SCHEDULED:" . ?↷)
("CLOSED:" . ?✓))
;; electric-pair-pairs
;; (append electric-pair-pairs
;; (mapcar (lambda (emph)
;; (let ((ch (string-to-char (car emph))))
;; (cons ch ch)))
;; org-emphasis-alist))
)
(: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))
(:face 'org-done '((t (:inherit (modus-themes-subtle-green))))
'org-tag '((t (:inherit (secondary-selection))))
'org-todo '((t (:inherit (modus-themes-subtle-red)))))
;; 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))
(:+leader "." #'+scratch-switch-to-scratch
"C-." #'+scratch-switch-to-scratch
"," #'+scratch-switch-to-text
"C-," #'+scratch-switch-to-text)
(+with-ensure-after-init
(+scratch-text-scratch))
(add-hook 'kill-buffer-query-functions #'+scratch-immortal))
(setup shell
(:option shell-command-prompt-show-cwd t)
(:local-set +modeline-position-function
(lambda () (string-replace (getenv "HOME")
"~"
default-directory)))
(:hook #'form-feed-mode))
(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-timer
+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 whitespace
(:option whitespace-line-column nil
whitespace-style '(face trailing tabs tab-mark))
(:hook-into text-mode prog-mode))
(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)
(+avy-buffer-face-mode +1)))
(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"))
(let
;; All available cape capfs listed here. Add them to the front since
;; they're reversed with `add-to-list'.
((append-fns '(cape-file
cape-dabbrev
cape-keyword))
(remove-fns '(cap-abbrev
cape-ispell
cape-dict)))
(dolist (fn append-fns)
(add-to-list 'completion-at-point-functions fn :append))
(dolist (fn remove-fns)
(setq completion-at-point-functions
(delete fn completion-at-point-functions)))
;; Fix position of t
(when (memq t completion-at-point-functions)
(setq completion-at-point-functions
(append (delq t completion-at-point-functions)
'(t))))))
(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
+modeline-position-function 'empty)
(: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
#'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))))
(tracking-mode +1)
(: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)
(+with-ensure-after-init
(: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)
(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)
(+with-eval-after-loads (consult +consult)
(:option consult-narrow-key "<"
consult-project-root-function '+consult-project-root)
(add-to-list 'consult-buffer-filter
(rx "*" (or "scratch" "text") "*"))
(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-flymake
consult-focus-lines
consult-git-grep
consult-grep
consult-imenu
consult-imenu-multi
consult-keep-lines
consult-line
consult-line-multi
consult-locate
consult-multi-occur
consult-outline
consult-ripgrep
consult-yank-pop))
(setf (alist-get buf-cmd vertico-multiform-commands) nil)))))
(setup (:straight consult-dir)
(:load-after consult)
(:+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))
("reddit\\.com" . markdown-mode)
("notabug\\.org" . markdown-mode)))
(+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)
(dolist (mode '(LaTeX-mode org-mode ruby-mode))
(add-hook (intern (format "%s-hook" mode))
(intern (format "embrace-%s-hook" mode))))
(:face 'embrace-help-pair-face '((t ( :inverse-video nil
:inherit font-lock-keyword-face))))
(:+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)
(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"))
(:quit "I don't think this works like how I want.")
(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))
(:quit) ; XXX: Somehow missing compat-26
(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"))
(:option frowny-eyes (rx (any ":=") (opt "'") (? "-")))
(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-insert-alist nil nil #'equal)
'(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-breadcrumbs-in-mode-line-mode nil
Info-fontify-emphasis-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)
(:option +jabber-pre-prompt "~ ~ ~\n")
(: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 nil ; colorizing doesn't match my color theme
jabber-chat-foreign-prompt-format (concat +jabber-pre-prompt
"%n\n"
(make-string +jabber-ws-prefix
?\ ))
jabber-chat-local-prompt-format (concat +jabber-pre-prompt
"%n\n"
(make-string +jabber-ws-prefix
?\ ))
jabber-groupchat-prompt-format (concat +jabber-pre-prompt
"%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)))
(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))
"🤔 ")))
file-percentage-mode nil
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)
;;; Alerting hooks --- remove echo messages
(remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo)
(remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo))
(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")))
(dolist (font emoji-fonts)
(when (member font ffl)
(set-fontset-font t 'symbol (font-spec :family font) nil :append))))
(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-active-user "acdw"
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."
;; Major mode in the mode-line
(defface +modeline-text-mode-face nil
"Text-mode major mode face.")
(defface +modeline-prog-mode-face nil
"Prog-mode major mode face.")
(modus-themes-with-colors
(custom-set-faces
`(font-lock-builtin-face
((,class :inherit modus-themes-bold
:foreground unspecified)))
`(font-lock-comment-face
((,class :inherit variable-pitch
:foreground ,fg-comment-yellow)))
`(font-lock-comment-delimiter-face
((,class :inherit font-lock-comment-face)))
`(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
:slant italic)))
`(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 :foreground ,fg-special-warm)))
`(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)))
`(+modeline-text-mode-face
((,class :foreground ,blue
:inherit modus-themes-bold)))
`(+modeline-prog-mode-face
((,class :foreground ,magenta
:inherit modus-themes-bold))))
(:option +modeline-major-mode-faces
`((text-mode . +modeline-text-mode-face)
(prog-mode . +modeline-prog-mode-face)
(t . bold))))))
(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 native-complete)
(with-eval-after-load 'shell
(native-complete-setup-bash))
(:with-hook shell-mode-hook
(:local-set completion-at-point-functions
(cons 'native-complete-at-point
completion-at-point-functions))))
(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 basic 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)
(:quit "I think I can do most of this myself.")
(: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)
(:load-after org user-save)
(:option org-visibility-state-file (.etc "org-visibility")
org-visibility-include-regexps '("\\.org\\'"))
(with-eval-after-load 'org-visibility
;; I have to add these hooks myself since I don't want it triggering on
;; /every/ save, but just when I `user-save'.
(add-hook 'user-save-hook #'org-visibility-save-noerror :append)
(add-hook 'kill-buffer-hook #'org-visibility-save-noerror :append)
(add-hook 'kill-emacs-hook #'org-visibility-save-all-buffers :append)
(add-hook 'find-file-hook #'org-visibility-load :append)
(add-hook 'first-change-hook #'org-visibility-dirty :append)
(add-hook 'org-cycle-hook #'org-visibility-dirty-org-cycle :append)))
(setup (:straight org-wc)
(:load-after org simple-modeline)
(:also-load +org-wc)
(add-hook 'org-mode-hook #'+org-wc-mode))
(setup (:straight orglink)
(:option orglink-activate-in-modes '(text-mode prog-mode))
(global-orglink-mode +1)
(global-goto-address-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++")))
(:also-load +pdf-tools)
(:with-mode pdf-view-mode
(:local-set +modeline-position-function #'+pdf-view-position))
(setf (alist-get "\\.pdf\\'" auto-mode-alist nil nil #'equal)
#'pdf-view-mode)
(pdf-tools-install :no-query))
(setup (:straight persistent-scratch)
(:require)
(:option persistent-scratch-save-file (sync/ "emacs/scratch")
persistent-scratch-backup-directory (sync/ "emacs/scratch.d/" t)
persistent-scratch-backup-file-name-format "%Y-%m-%dT%H:%M_%s")
(persistent-scratch-autosave-mode +1)
(+mapc-some-buffers (lambda () (persistent-scratch-mode +1))
persistent-scratch-scratch-buffer-p-function))
(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-anzu
)
( ; right
(lambda ()
(unless +tab-bar-misc-info-mode
(+modeline-concat
'(+modeline-track
simple-modeline-segment-misc-info))))
+modeline-position
simple-modeline-segment-process
,(+modeline-concat
'(+modeline-god-mode
+modeline-kmacro-indicator
+modeline-reading-mode
+modeline-narrowed
+modeline-text-scale
+modeline-input-method)
" ")
+modeline-major-mode
+modeline-spacer)))
(simple-modeline-mode +1))
(setup (:straight slack)
(:also-load +slack)
(:option slack-prefer-current-team t
slack-buffer-emojify t
slack-thread-also-send-to-room nil
slack-typing-visibility 'buffer
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
#'suspend-frame)
(sophomore-disable-with 'confirm
#'+save-buffers-quit
#'save-buffers-kill-terminal)
(sophomore-mode +1))
(setup (:straight (spongebob-case
:host github
:repo "duckwork/spongebob-case.el")))
(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 30
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 +titlecase)
(:with-map +casing-map
(:bind "t" #'titlecase-dwim
"M-t" #'titlecase-dwim
"s" #'+titlecase-sentence-style-dwim
"M-s" #'+titlecase-sentence-style-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)
(:+leader "t" #'trashed)
(:option trashed-action-confirmer #'y-or-n-p
trashed-use-header-line t
trashed-size-format 'human-readable))
(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)
(:face 'undo-hl-delete '((t :strikethrough t))
'undo-hl-insert '((t :underline t)))
(: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))