;;; init.el --- Emacs initiation file -*- lexical-binding: t -*- ;; Author: Case Duckworth ;; 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) (+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-1" #'delete-other-windows ;; "C-x 2" #'+split-window-below-then ;; "C-x C-2" #'+split-window-below-then ;; "C-x 3" #'+split-window-right-then ;; "C-x C-3" #'+split-window-right-then ) ;; Font-lock keywords (add-hook 'prog-mode-hook #'font-lock-todo-insinuate) ;; 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-?) ;; Hooks ;; 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) (: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) (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)) (+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-globl-mark)) (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) (user-save-global-mode +1)) (setup +key (+ensure-after-init #'+key-global-mode)) (setup abbrev (:option abbrev-file-name (sync/ "abbrev.el") save-abbrevs 'silent) (: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. "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. (with-eval-after-load 'org-contacts (require 'chd) (+browse-url-set-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 chd/url-regexps #'browse-url-chrome) (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)))))) ;; 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 (:option compilation-always-kill t compilation-ask-about-save nil compilation-scroll-output t)) (setup dired (:also-load dired-x) (:also-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 "" #'dired-up-directory) (:hook #'dired-hide-details-mode #'hl-line-mode #'lin-mode) (:+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 ecomplete (:quit) (:load-after org-contacts) (:also-load +ecomplete) (:option message-mail-alias-type 'ecomplete message-self-insert-commands nil message-expand-name-standard-ui t) (with-eval-after-load 'ecomplete (:option completion-category-defaults nil) (with-eval-after-load 'embark (:bind-into embark-email-map "+" #'+ecomplete-add-email "\\" #'+ecomplete-remove-email))) (add-hook 'message-sent-hook #'message-put-addresses-in-ecomplete)) (setup ehelp ;; Trying this instead of `helpful' (:global [help] 'ehelp-command [f1] 'ehelp-command) (with-eval-after-load 'vertico-multiform (dolist (cmd '(electric-describe-key electric-describe-mode electric-describe-syntax electric-describe-bindings electric-describe-function electric-describe-variable)) (setf (alist-get cmd vertico-multiform-commands) nil)))) (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 ?# ?$) (* " "))) (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 (list (cons 'outline-regexp eshell-prompt-regexp) (cons 'page-delimiter eshell-prompt-regexp) (cons 'imenu-generic-expression (list "Prompt" (concat eshell-prompt-regexp "\\(.*\\)") 1)) (cons 'truncate-lines t))) (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-" #'+hs-cycle "C-S-" #'+hs-global-cycle ;; but y tho "C-S-" #'+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) (: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) (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 org-contacts) (: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) ;; 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 "lists" :query (+notmuch-query-concat "tag:/List/" "tag:unread") :key "l") ;; original (list :name "inbox" :query (+notmuch-query-concat "tag:inbox" "NOT tag:Spam") :key "i") (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)) (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/*"))) (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 _work) (:option org-adapt-indentation nil 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-list-demote-modify-bullet '(("-" . "+") ("+" . "-")) org-log-done 'time org-log-into-drawer 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@/!)" "|" "DONE(d!)") (sequence "|" "CANCELED(k@)") (sequence "MEETING(m)")) org-emphasis-alist '(("*" org-bold) ("/" org-italic) ("_" org-underline) ("=" org-verbatim) ("~" org-code) ("+" org-strikethrough))) (:bind "RET" #'+org-return-dwim "" #'+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) (:hook #'variable-pitch-mode) (: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))) (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. ("^ *\\([-]\\) " (0 (progn (compose-region (match-beginning 1) (match-end 1) "∙") 'default))) ("^ *\\([+]\\) " (0 (progn (compose-region (match-beginning 1) (match-end 1) "◦") 'default))))) (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-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) (dolist (var '(org-agenda-files org-agenda-file-regexp org-agenda-templates)) (add-to-list '+custom-variable-allowlist var)) (with-eval-after-load 'org (add-to-list 'org-agenda-files (sync/ "org/" t))) (:+leader "a" #'org-agenda "C-a" #'org-agenda) (:hook #'hl-line-mode) (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-contacts (:load-after org) (:also-straight org-vcard) ; for importing Vcard files (:option org-contacts-matcher "contact") ; Contacts are tagged "contact" ) (setup org-export (:also-load ox-md) (:option org-export-coding-system 'utf-8-unix org-export-headline-levels 8 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 'user-save (advice-add 'org-export-dispatch :before 'user-save-run-hooks))) (setup password-cache (:option password-cache t password-cache-expiry (* 60 60))) (setup prog (:local-set comment-auto-fill-only-comments t) (:hook #'prettify-symbols-mode #'turn-on-auto-fill)) (setup scratch (:require +scratch) (:option initial-major-mode #'lisp-interaction-mode initial-scratch-message (concat (replace-regexp-in-string "^" ";; " (string-trim (if (executable-find "fortune") (shell-command-to-string "fortune -s") "ABANDON ALL HOPE YE WHO ENTER HERE"))) "\n\n")) (add-hook 'kill-buffer-query-functions #'+scratch-immortal)) (setup shr (:option shr-width (- fill-column 5) ; pad out for wide letters shr-use-fonts t)) (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-menu-bar-icon " ☰ " tab-bar-close-button (propertize " ¬ " 'display t 'close-tab t) 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 text (:hook #'turn-on-auto-fill)) (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) (:+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-when 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 _type) (setq input (orderless-pattern-compiler input)) (cons input (lambda (str) (orderless--highlight input str))))) (with-eval-after-load 'affe (setf (alist-get 'affe-grep vertico-multiform-commands) '(buffer) (alist-get 'affe-find vertico-multiform-commands) '(buffer)) (:+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 (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 (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-" #'+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) (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 clhs)) (setup (:straight consult) (:also-load +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) ("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) ;;(" 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 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) '(buffer)))))) (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) '(buffer) (alist-get 'consult-notmuch-tree vertico-multiform-commands) '(buffer)))) (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) (:+leader "s" #'crux-visit-shell-buffer) (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 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-when 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) (: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) (:hook #'reading-mode) (:option +elfeed--update-repeat (* 60 30) ; 1/2 hour +elfeed--update-first-time 60)) (+elfeed-update-async-mode +1)) (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)) (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 " 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 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))) (setup (:straight eros) (: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) (:load-after eshell) (defalias 'eshell/v 'eshell-exec-visual) (eshell-vterm-mode +1)) (setup (:straight-when 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-dwim)) (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 "" #'+flyspell-correct-buffer) (with-eval-after-load 'vertico-multiform (setf (alist-get 'flyspell vertico-multiform-categories) nil))) (setup (:straight-when (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) (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") geiser-chicken macrostep-geiser scheme-complete) (setf (alist-get "\\.scm\\'" auto-mode-alist nil nil #'string=) 'scheme-mode)) (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) (:quit "Trying `electric-help' instead.") (:+key " f" #'helpful-callable " v" #'helpful-variable " k" #'helpful-key " ." #'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)))) (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 info+) (:load-after info)) (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://tildegit.org/wgreenhouse/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 (:repo "https://tildegit.org/acdw/emacs-jabber" :host nil))) (:also-load +jabber) (:option jabber-account-list '(("acdw@hmm.st")) jabber-groupchat-buffer-format "xmpp:%n" jabber-chat-buffer-format "xmpp:%n" jabber-muc-private-buffer-format "xmpp:%n(%g)" jabber-activity-show-p #'ignore jabber-muc-decorate-presence-patterns '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$") ("." . jabber-muc-presence-dim))) (dolist (mode '(jabber-chat-mode jabber-browse-mode jabber-roster-mode jabber-console-mode)) (add-hook (intern (format "%s-hook" mode)) #'visual-fill-column-mode)) (add-hook 'jabber-activity-mode-hook 'tracking-mode) (:+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-when keychain-environment (executable-find "keychain")) (keychain-refresh-environment)) (setup (:straight lacarte) (:+key "" #'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 gitlab :repo "protesilaos/lin")) (require 'lin) (+with-ensure-after-init (dolist (hook lin-foreign-hooks) (add-hook hook #'hl-line-mode) (add-hook hook #'lin-mode)))) (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 "Janky a.f.") ;; `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 gitlab :repo "protesilaos/modus-themes")) (require 'modus-themes (.etc "straight/build/modus-themes/modus-themes")) (:also-load dawn) (:option modus-themes-mixed-fonts t modus-themes-bold-constructs t modus-themes-italic-constructs t modus-themes-headings '((t . (background)))) (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))))) (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)) (:global "C-a" #'+mwim-beginning-maybe "C-e" #'+mwim-end-maybe)) (setup (:straight notmuch-bookmarks) (:load-after notmuch) (:when-loaded (notmuch-bookmarks-mode +1))) (setup (:straight notmuch-labeler) (:load-after notmuch)) (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) (: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-sticky-header) ;; (:hook-into org-mode) ) (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 paredit) (:bind "DEL" #'paredit-backward-delete "C-" #'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)) (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-when pdf-tools ;; Ensure we can build `pdf-tools' (or (executable-find "gcc") (executable-find "g++"))) (setf (alist-get "\\.pdf\\'" auto-mode-alist nil nil #'equal) #'pdf-view-modei) (pdf-tools-install t)) (setup (:straight (plancat :host github :repo "duckwork/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 (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 ";" 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-reading-mode +modeline-narrowed) ",") ,(+modeline-concat '(+modeline-region +modeline-line-column +modeline-file-percentage)) ))) (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 "~/var/download/" slack-display-team-name nil) (with-eval-after-load '+slack (+slack-register-teams))) (setup (:straight-when 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" "")) (: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-when 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) (: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 (executable-find "gzip")) (global-undo-fu-session-mode +1)) (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")))) (:also-load +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) '(buffer)) (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-when vterm (and module-file-suffix (executable-find "cmake"))) (: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-when w3m (executable-find "w3m")) ;; (+with-ensure-after-init ;; (:option browse-url-browser-function #'w3m-browse-url ;; +browse-url-browser-function browse-url-browser-function)) ) (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 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)) (setup (:straight zzz-to-char) (:require +zzz-to-char) (:option zzz-to-char-reach (+bytes 1 :kib)) (:global "M-z" #'+zzz-to-char))