;;; init.el -*- lexical-binding: t; coding: utf-8-unix -*- ;; Author: Case Duckworth ;; Created: Sometime during Covid-19, 2020 ;; Keywords: configuration ;; URL: https://tildegit.org/acdw/emacs ;; Bankruptcy: 7 ;; This file is NOT part of GNU Emacs. ;;; License: ;; Everyone is permitted to do whatever with this software, without ;; limitation. This software comes without any warranty whatsoever, ;; but with two pieces of advice: ;; - Don't hurt yourself. ;; - Make good choices. ;;; Code: ;;; Setup ;;;; `setup' (straight-use-package '(setup :host nil :repo "https://git.sr.ht/~pkal/setup")) (require 'setup) (setup setup (setup-define :straight (lambda (recipe) `(straight-use-package ',recipe)) :documentation "Install RECIPE with `straight-use-package'." :repeatable t :shorthand (lambda (sexp) (let ((recipe (cadr sexp))) (if (consp recipe) (car recipe) recipe))))) ;;;; `no-littering' (setup (:straight no-littering) (:option no-littering-etc-directory (acdw/dir) no-littering-var-directory (acdw/dir)) (require 'no-littering)) ;;;; My packages (when-let ((default-directory (expand-file-name-exists-p "pkg/" user-emacs-directory))) (normal-top-level-add-subdirs-to-load-path)) ;;;; Private stuff (acdw/require-private) ;;;; Compatibility with older versions (require 'acdw-compat) ;;;; Lisp (require 'acdw-lisp) ;;; Basics ;; NOTE that some of the names in `setup' forms are arbitrary. (setup acdw (:option user-full-name "Case Duckworth" user-mail-address "acdw@acdw.net")) (setup (:require auth-source) (:option auth-sources '("~/.authinfo" "~/.authinfo.gpg"))) (setup autorevert (:option global-auto-revert-non-file-buffers t) (global-auto-revert-mode +1)) (setup browse-url (require 'acdw-browse-url) (setq-default browse-url-secondary-browser-function (if (executable-find "firefox") ; prefer Firefox #'browse-url-firefox #'browse-url-default-browser) browse-url-new-window-flag nil ; for eww browse-url-firefox-arguments '("--new-tab") ; for firefox browse-url-firefox-new-window-is-tab t) (acdw/browse-url-set-handlers `(("\\.jpe?g\\'" . ,(if (executable-find "feh") #'browse-url-feh #'eww-browse-url)) ("youtube\\.com\\|youtu\\.be" . ,(if (executable-find "mpv") #'browse-url-mpv #'eww-browse-url)) ("google\\.com" . browse-url-default-browser) ("\\(twitter\\.com\\|t\\.co\\)" . acdw/eww-browse-twitter-url) ("." . eww-browse-url))) ;; Buttonize gemini:// links. (acdw/add-button-url-regexp-protocol "gemini")) (setup buffers (:global "C-x k" acdw/kill-a-buffer)) (setup calendar (:option calendar-week-start-day 1 ; Monday )) (setup completion (:option completion-ignore-case t read-buffer-completion-ignore-case t completion-styles '(substring partial-completion) completion-category-defaults nil completion-category-overrides '((file (styles . (partial-completion))))) (:global "M-/" hippie-expand)) (setup cursor (:option cursor-type 'bar cursor-in-non-selected-windows 'hollow blink-cursor-blinks 1) (blink-cursor-mode +1)) (setup cus-edit (:option custom-file null-device ; don't store customizations custom-magic-show nil custom-magic-show-button t custom-raised-buttons nil custom-unlispify-tag-names nil custom-variable-default-form 'lisp) ;; `Custom-mode-hook' fires /before/ the widgets are built, so I have to ;; install advice after the widgets are made. (advice-add 'custom-buffer-create :after (defun custom-buffer@expand-widgets (&rest _) "Expand descriptions and values of variables in `Custom-mode' buffers." (interactive) ;; "More/Hide" widgets (thanks alphapapa!) (widget-map-buttons (lambda (widget _) (pcase (widget-get widget :off) ("More" (widget-apply-action widget))) nil)) ;; "Show Value" widgets (the little triangles) (widget-map-buttons (lambda (widget _) (pcase (widget-get widget :off) ("Show Value" (widget-apply-action widget))) nil)))) (add-hook ; thanks u/oantolin! 'Custom-mode-hook (defun custom-mode@imenu () "Build `imenu' for `Custom-mode'." (setq imenu-generic-expression '(("Faces" "^\\(?:Show\\|Hide\\) \\(.*\\) face: \\[sample\\]" 1) ("Variables" "^\\(?:Show Value\\|Hide\\) \\([^:\n]*\\)" 1)))))) (setup debugger (:hook visual-line-mode) (:global "C-c d" toggle-debug-on-error)) (setup dired (setq-default dired-recursive-copies 'always dired-recursive-deletes 'always delete-by-moving-to-trash t dired-listing-switches "-Al" ls-lisp-dirs-first t dired-ls-F-marks-symlinks t dired-no-confirm '(byte-compile chgrp chmod chown copy hardlink load move shell touch symlink) dired-dwim-target t) (:hook dired-hide-details-mode hl-line-mode) (:global "C-x C-j" dired-jump) (acdw/system (:work (:straight w32-browser) (autoload 'dired-w32-browser "w32-browser") (:bind "RET" dired-w32-browser)) (:home (:straight dired-open) (require 'dired-open) (:bind "RET" dired-find-alternate-file))) (with-eval-after-load 'dired (require 'dired-x) (:straight dired-subtree) (:bind "i" dired-subtree-toggle "TAB" dired-subtree-cycle) (:straight dired-collapse) (:hook dired-collapse-mode) (:straight dired-git-info) (:bind ")" dired-git-info-mode) (:straight trashed) (:option trashed-action-confirmer #'y-or-n-p))) (setup disabled ;; While this stuff is defined in novice.el, I'm using 'disabled' as the name ;; for easy finding. ;; Enable all disabled commands. ;; This is an option, but I'm going to try /enabling/ just the ones that I ;; use instead. ;; (mapatoms (lambda (symbol) ;; (when (get symbol 'disabled) ;; (put symbol 'disabled nil)))) ;; Enable /some/ disabled commands (dolist (enable-sym '(narrow-to-region dired-find-alternate-file narrow-to-page)) (put enable-sym 'disabled nil)) ;; Now, disable symbols as I wish. (dolist (disable-sym '(view-hello-file suspend-frame scroll-left scroll-right comment-set-column set-fill-column)) (put disable-sym 'disabled t)) ;; And set the disabled function to something better than the default. ;; Now, I can run any disabled command, but I have to use M-x to do it. (setq disabled-command-function (defun acdw/disabled-command-function (&optional cmd keys) (let ((cmd (or cmd this-command)) (keys (or keys (this-command-keys)))) ;; this logic stolen from original `disabled-command-function' (if (or (eq (aref keys 0) (if (stringp keys) (aref "\M-x" 0) ?\M-x)) (and (>= (length keys) 2) (eq (aref keys 0) meta-prefix-char) (eq (aref keys 1) ?x))) ;; it's been run as an M-x command, we want to do it (call-interactively cmd) ;; else, tell the user it's disabled. (message (substitute-command-keys (concat "Command `%s' has been disabled. " "Run with \\[execute-extended-command].")) cmd)))))) (setup ediff (:option ediff-window-setup-function 'ediff-setup-windows-plain ediff-split-window-function 'split-window-horizontally)) (setup eldoc (:option eldoc-idle-delay 0.1 eldoc-echo-area-use-multiline-p nil)) (setup elisp-mode (:option eval-expression-print-length nil eval-expression-print-level nil lisp-indent-function #'lisp-indent-function) (add-hook 'emacs-lisp-mode-hook (defun emacs-lisp@enforce-lexical-binding () (setq lexical-binding t))) (add-hook 'emacs-lisp-mode-hook (defun emacs-lisp@imenu-add-setup () (add-to-list 'imenu-generic-expression '("Setup" "\\(^\\s-*(setup +(?\\)\\(\\_<.+\\_>\\)" 2)))) ;; Emulate slime's eval binds (:with-map emacs-lisp-mode-map (:bind "C-c C-c" eval-defun "C-c C-k" acdw/eval-region-or-buffer "C-c C-z" ielm)) (add-hook 'emacs-lisp-mode-hook #'turn-on-eldoc-mode) (add-hook 'ielm-mode-hook #'turn-on-eldoc-mode) (setup (:straight macrostep) (define-key emacs-lisp-mode-map (kbd "C-c e") #'macrostep-expand)) (setup (:straight eros) (:hook-into emacs-lisp-mode)) ;; Add advice to pulse evaluated regions (define-advice eval-region (:around (fn start end &rest args) pulse-region) (pulse-momentary-highlight-region start end) (apply fn start end args)) (setup (:straight elisp-slime-nav) (:hook-into emacs-lisp-mode ielm-mode))) (setup encoding (:option locale-coding-system 'utf-8-unix coding-system-for-read 'utf-8-unix coding-system-for-write 'utf-8-unix buffer-file-coding-system 'utf-8-unix default-process-coding-system '(utf-8-unix . utf-8-unix) x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)) (set-charset-priority 'unicode) (set-language-environment "UTF-8") (prefer-coding-system 'utf-8-unix) (set-default-coding-systems 'utf-8-unix) (set-terminal-coding-system 'utf-8-unix) (set-keyboard-coding-system 'utf-8-unix) (acdw/system (:work (set-clipboard-coding-system 'utf-16-le) (set-selection-coding-system 'utf-16-le)) (_ (set-selection-coding-system 'utf-8) (set-clipboard-coding-system 'utf-8)))) (setup erc (:require acdw-erc) (:also-load erc-autoaway erc-track erc-goodies) (:option erc-auto-discard-away t erc-autoaway-idle-seconds 600 erc-autoaway-message "BRB (autoaway: %i seconds)" erc-button-url-regexp browse-url-button-regexp erc-common-server-suffixes '(("tilde.chat\\'" . "~") ("libera.chat\\'" . "LC")) erc-default-server "irc.tilde.chat" erc-fill-function #'erc-fill-static erc-fill-static-center 14 erc-format-nick-function #'erc-format-truncate-@nick erc-header-line-face-method #'erc/update-header-line-show-disconnected erc-hide-list '("NICK" "MODE" "JOIN" "PART" "QUIT") erc-interpret-controls-p t erc-interpret-mirc-color t erc-join-buffer 'bury erc-kill-buffer-on-part t erc-kill-queries-on-quit t erc-kill-server-buffer-on-quit t erc-nick "acdw" erc-nick-truncate (- erc-fill-static-center 1) erc-pcomplete-nick-postfix ":" erc-prompt #'acdw-erc/prompt erc-prompt-for-password nil ; use ~/.authinfo erc-rename-buffers t erc-server erc-default-server erc/servers (when (boundp 'erc-autojoin-channels-alist) (mapcar #'car erc-autojoin-channels-alist)) erc-server-coding-system '(utf-8 . utf-8) erc-timestamp-intangible t erc-track-exclude-types (append erc-hide-list '("AWAY" "353" "324" "329" "332" "333" "477")) erc-track-exclude-server-buffer t erc-track-position-in-mode-line 'before-modes erc-track-visibility nil ; only the selected frame ) ;; Thanks bpalmer! (advice-add 'show-paren-function :around (defun show-paren@skip-in-erc (f &rest r) "Skip `show-paren-mode' in `erc-mode'." (unless (derived-mode-p 'erc-mode) (apply f r)))) (add-hook 'kill-emacs-hook #'erc/disconnect) (add-hook 'erc-mode-hook (defun erc-mode@setup () (setq-local scroll-margin 0))) (:hook erc-autoaway-mode erc-track-mode erc-truncate-mode) ;; disable ERC tracking when working ... BE PRODUCTIVE! (add-hook 'org-clock-in-hook #'erc-track-disable) (add-hook 'org-clock-out-hook #'erc-track-enable) (:bind "C-c C-b" acdw-erc/erc-switch-to-buffer "C-c C-c" nil ; def: `erc-toggle-interpret-controls' ) (with-eval-after-load 'erc (when (acdw/system :work) ;; IDK why, but on 28 this bit doesn't work -- which is at home. (setup (:straight erc-hl-nicks) (:option (append erc-modules) 'hl-nicks erc-hl-nicks-minimum-contrast-ratio 4.5 erc-hl-nicks-color-contrast-strategy '(invert contrast)) (:hook-into erc-mode) ;; Refresh nick colors after changing modus theme ;; Ideally, there'd be a hook for /any/ time we changed theme... but ;; whatever. (add-hook 'modus-themes-after-load-theme-hook #'erc-hl-nicks-refresh-colors))) ;; (setup (:straight erc-image) ;; (:option (append erc-modules) 'image ;; erc-image-inline-rescale 300)) ;; Rewrite `erc-quit/part-reason-default' (defun erc-quit/part-reason-default () "Default quit/part message." (format "\C-iSee You, Space Cowpokes. . .\C-i")) (erc-update-modules))) (setup eshell (:also-load acdw-eshell em-smart) (:option eshell-aliases-file (acdw/dir "eshell/aliases" t) eshell-directory-name (acdw/dir "eshell/" t) eshell-kill-on-exit nil eshell-review-quick-commands nil eshell-smart-space-goes-to-end t eshell-where-to-jump 'begin) (:global "C-c s" eshell-pop-or-quit) (defun eshell-mode@setup () "Set up `eshell' for use. Most customizations must go in this function since `eshell' loads like a dumbass." ;; Define keys (dolist (spec '(("C-d" . eshell-quit-or-delete-char))) (define-key eshell-mode-map (kbd (car spec)) (cdr spec))) ;; Fix modeline (when (boundp 'simple-modeline--mode-line) (setq mode-line-format '(:eval simple-modeline--mode-line))) ;; Make navigating amongst prompts easier (setq-local outline-regexp eshell-prompt-regexp page-delimiter eshell-prompt-regexp)) (defun eshell-buffer-name () (rename-buffer (concat "*eshell*<" (eshell/pwd) ">") t)) (add-hook 'eshell-directory-change-hook #'eshell-buffer-name) (add-hook 'eshell-prompt-load-hook #'eshell-buffer-name) (:hook eshell-mode@setup eshell-arg-hist-mode)) (setup eww (:option eww-search-prefix "https://duckduckgo.com/html?q=" url-privacy-level '(email agent cookies lastloc)) (:hook acdw/reading-mode)) (setup files (:option auto-save-file-name-transforms `((".*" ,(acdw/dir "auto-save/" t) t)) auto-save-list-file-prefix (acdw/dir "auto-save-list/.saves-" t) auto-save-interval 60 auto-save-timeout 60 auto-save-visited-interval auto-save-timeout backup-by-copying t backup-directory-alist `((".*" . ,(acdw/dir "backup/" t))) delete-old-versions t mode-require-final-newline 'visit-save tramp-backup-directory-alist backup-directory-alist vc-make-backup-files t version-control t) (:global "C-c i" acdw/find-emacs-dotfiles) (auto-save-visited-mode +1) (add-hook 'unfocused-hook (defun unfocused@save-buffers () (save-some-buffers t)))) (setup flyspell (add-hook 'text-mode-hook #'flyspell-mode)) (setup (:straight flyspell-correct) (add-hook 'flyspell-mode-hook (defun flyspell-mode@flyspell-correct () (dolist (keybind '(("C-;" . flyspell-correct-wrapper) ("C-," . nil) ("C-." . nil) ("C-M-i" . nil))) (define-key flyspell-mode-map (kbd (car keybind)) (cdr keybind)))))) ;; (setup flyspell ;; ;; follow the directions here: https://old.reddit.com/r/emacs/comments/dgj0ae ;; ;; in short: ;; ;; - download hunspell from GitHub and put in ~/usr/bin ;; ;; - download LibreOffice English dictionaries and put in ;; ;; ~/usr/share/hunspell ;; (setq-default ;; flyspell-issue-message-flag nil ;; ispell-program-name "hunspell" ;; ispell-dictionary "default" ;; ispell-personal-dictionary "~/.hunspell_personal" ;; ispell-local-dictionary-alist '(("default" ;; "[[:alpha:]]" "[^[:alpha:]]" ;; "[']" nil ;; ("-d" "en_US") nil utf-8))) ;; (acdw/system ;; (:work (let ((dicpath (expand-file-name "~/usr/share/hunspell/"))) ;; (setenv "DICPATH" dicpath)))) ;; ;; new variable `ispell-hunspell-dictionary-alist' is defined in Emacs ;; ;; If it's nil, Emacs tries to automatically set up the dictionaries. ;; (when (boundp 'ispell-hunspell-dictionary-alist) ;; (setq ispell-hunspell-dictionary-alist ispell-local-dictionary-alist)) ;; (:needs ispell-program-name) ; don't proceed if not installed ;; (unless (file-exists-p ispell-personal-dictionary) ;; (write-region "" nil ispell-personal-dictionary nil 0)) ;; (when (executable-find ispell-program-name) ;; (add-hook 'text-mode-hook #'flyspell-mode) ;; (add-hook 'prog-mode-hook #'flyspell-prog-mode)) ;; (:when-loaded ;; (setup (:straight flyspell-correct) ;; (:with-map flyspell-mode-map ;; (:bind "C-;" flyspell-correct-wrapper ;; ;; Remove all other binds ;; "C-," nil ;; "C-." nil ;; "C-M-i" nil))))) ;; (setup flyspell ;; (:option ;; flyspell-issue-message-flag nil ;; ispell-program-name "aspell" ;; ispell-dictionary "en_US" ;; ispell-personal-dictionary "~/.dictionary" ;; ispell-extra-args '("--sug-mode=ultra" "--lang=en_US")) ;; (:needs ispell-program-name) ;; (unless (file-exists-p ispell-personal-dictionary) ;; (write-region "" nil ispell-personal-dictionary nil 0)) ;; (add-hook 'text-mode-hook #'flyspell-mode) ;; (add-hook 'prog-mode-hook #'flyspell-prog-mode) ;; (:when-loaded ;; (setup (:straight flyspell-correct) ;; (:with-map flyspell-mode-map ;; (:bind "C-;" flyspell-correct-wrapper ;; ;; Remove other binds ;; "C-," nil ;; "C-." nil ;; "C-M-i" nil))))) (setup frames (:option frame-title-format '("%b@" (:eval (or (file-remote-p default-directory 'host) system-name)) " %+%* GNU Emacs" (:eval (when (frame-parameter nil 'client) " Client"))) window-resize-pixelwise t) (add-hook 'unfocused-hook #'garbage-collect)) (setup gnus (:option gnus-home-directory (acdw/dir "gnus" t) gnus-directory (acdw/dir "News" t) gnus-init-file (expand-file-name "gnus.el" user-emacs-directory)) (:global "C-c n" gnus)) (setup goto-addr (add-hook 'text-mode-hook #'goto-address-mode) (add-hook 'prog-mode-hook #'goto-address-prog-mode)) (setup ibuffer (:also-load ibuf-ext) (:option ibuffer-saved-filter-groups '(("default" ("dired" (mode . dired-mode)) ("customize" (mode . Custom-mode)) ("emacs" (or (name . "^\\*scratch\\*$") (name . "^\\*Messages\\*$") (name . "^\\*Warnings\\*$") (name . "^\\*straight-process\\*$") (name . "^\\*Calendar\\*$"))) ("git" (or (name . "^\*magit") (name . "^\magit"))) ("help" (or (mode . help-mode) (mode . Info-mode) (mode . helpful-mode))) ("messaging" (or (mode . message-mode) (mode . bbdb-mode) (mode . mail-mode) (mode . gnus-group-mode) (mode . gnus-summary-mode) (mode . gnus-article-mode) (name . "^\\.bbdb$") (name . "^\\.newsrc-dribble") (mode . erc-mode))) ("shell" (or (mode . eshell-mode) (mode . shell-mode) (mode . vterm-mode))) ("web" (or (mode . elpher-mode) (mode . gemini-mode) (mode . eww-mode)))))) (global-set-key (kbd "C-x C-b") (defun list-buffers-or-ibuffer (arg) "`list-buffers', or with a prefix arg, `ibuffer'." (interactive "P") (if arg (ibuffer) (electric-buffer-list nil)))) (add-hook 'ibuffer-mode-hook (defun ibuffer@filter-to-default () (ibuffer-switch-to-saved-filter-groups "default"))) (:option ibuffer-show-empty-filter-groups nil ibuffer-expert t)) (setup imenu (:option imenu-auto-rescan t)) (setup Info (:hook variable-pitch-mode acdw/reading-mode)) (setup isearch (:option search-default-mode t)) (setup lines (:option fill-column 79 word-wrap t truncate-lines nil) (global-display-fill-column-indicator-mode +1) (global-so-long-mode +1) (add-hook 'visual-line-mode-hook (defun acdw/disable-fill-column-indicator () (display-fill-column-indicator-mode (if visual-line-mode -1 +1)))) ;; `acdw/kill-line-and-join-advice' cribs from `crux-kill-and-join-forward'. ;; I can't simply advise `kill-line' with an override from crux because crux ;; itself calls `kill-line', leading to a infinite nesting situation. (define-advice kill-line (:around (fn &rest args) join-killed-line) (if (and (eolp) (not (bolp))) (delete-indentation 1) (apply fn args)))) (setup minibuffer (:option enable-recursive-minibuffers t file-name-shadow-properties '(invisible t intangible t) minibuffer-eldef-shorten-default t minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt) read-answer-short t read-extended-command-predicate ; used on >28 #'command-completion-default-include-p) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) (add-hook 'minibuffer-setup-hook #'acdw/gc-disable) (add-hook 'minibuffer-exit-hook #'acdw/gc-enable) (minibuffer-depth-indicate-mode +1) (file-name-shadow-mode +1) (minibuffer-electric-default-mode +1) (fset 'yes-or-no-p #'y-or-n-p)) (setup page (:option page-delimiter (rx bol (or "\f" ";;;") (not (any "#")) (* not-newline) "\n" (* (* blank) (opt ";" (* not-newline)) "\n"))) (defun recenter-to-top (&rest _) "Recenter the cursor to the top of the window." (when (called-interactively-p 'any) (recenter (if (or (null scroll-margin) (zerop scroll-margin)) 3 scroll-margin)))) (:advise forward-page :after #'recenter-to-top backward-page :after #'recenter-to-top) ;; I'm not sure where this is in /my/ version of Emacs (defvar page-navigation-repeat-map (let ((map (make-sparse-keymap))) (define-key map "]" #'forward-page) (define-key map "[" #'backward-page) map) "Keymap to repeat page navigation key sequences. Used in `repeat-mode'.") (put 'forward-page 'repeat-map 'page-navigation-repeat-map) (put 'backward-page 'repeat-map 'page-navigation-repeat-map)) (setup prog (:option smie-indent-basic tab-width) (add-hook 'prog-mode-hook (defun prog-mode@auto-fill () (setq-local comment-auto-fill-only-comments t) ;; (advice-add 'do-auto-fill :after ;; (defun auto-fill@set-comment-column (&rest _) ;; (save-excursion ;; (when (or (looking-back comment-start-skip) ;; (progn (backward-word) ;; (looking-back ;; comment-start-skip))) ;; (comment-set-column t))))) ;; If the above advice is enabled, the below advice also needs to ;; be set to make `comment-dwim' work... I think. ;; (advice-add 'comment-dwim :before ;; (defun comment-dwim@set-comment-column (&rest _) ;; (setq comment-column 0))) (turn-on-auto-fill))) (:option show-paren-delay 0 show-paren-style 'mixed show-paren-when-point-inside-paren t show-paren-when-point-in-periphery t) (defun flymake-mode-except () "Turn on flymake mode, except in some modes." (let ((no-flymake-modes '(emacs-lisp-mode))) (unless (or (member major-mode no-flymake-modes) (apply #'derived-mode-p no-flymake-modes)) (flymake-mode-on)))) (:hook show-paren-mode electric-pair-local-mode flymake-mode-except acdw/setup-fringes) (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)) (setup re-builder (require 'acdw-re) (advice-add 're-builder :before #'acdw/re-builder-save-state) (add-hook 'reb-mode-hook #'paredit-mode) (:global "" re-builder) (dolist (map '(reb-mode-map reb-lisp-mode-map)) (let ((setup-map map)) (:bind "RET" reb-replace-regexp "M-n" reb-next-match "M-p" reb-prev-match "C-g" reb-quit "C-c C-k" reb-quit)))) (setup (:require recentf) (:option recentf-save-file (acdw/dir "recentf.el") recentf-max-menu-items 100 recentf-max-saved-items nil recentf-auto-cleanup 'mode (append recentf-exclude) (acdw/dir)) (advice-add 'dired-rename-file :after #'rjs/recentf-rename-notify) (recentf-mode +1)) (setup repeat ;; new for Emacs 28! (:only-if (fboundp #'repeat-mode)) (:option repeat-exit-key "g" repeat-exit-timeout 5) (repeat-mode +1)) (setup (:require savehist) (:option history-length t history-delete-duplicates t savehist-autosave-interval 60 savehist-file (acdw/dir "savehist.el")) (dolist (var '(extended-command-history global-mark-ring kill-ring regexp-search-ring search-ring mark-ring)) (add-to-list 'savehist-additional-variables var)) (savehist-mode +1)) (setup saveplace (:option save-place-file (acdw/dir "places.el") save-place-forget-unreadable-files (acdw/system :home)) (save-place-mode +1)) (setup scratch (:option inhibit-startup-screen t initial-buffer-choice t initial-scratch-message (concat ";; Howdy, " (nth 0 (split-string user-full-name)) "! " "Welcome to GNU Emacs.\n\n") initial-major-mode 'emacs-lisp-mode) (add-hook 'kill-buffer-query-functions (defun kill-buffer-query@immortal-scratch () (if (eq (current-buffer) (get-buffer "*scratch*")) (progn (bury-buffer) nil) t)))) (setup scrolling (:option auto-window-vscroll nil fast-but-imprecise-scrolling t scroll-margin 3 scroll-conservatively 101 scroll-preserve-screen-position 1)) (setup selection (:option save-interprogram-paste-before-kill t yank-pop-change-selection t x-select-enable-clipboard t x-select-enable-primary t mouse-drag-copy-region t kill-do-not-save-duplicates t) (delete-selection-mode +1)) (setup (:require server) (unless (server-running-p) (server-start))) (setup sh-mode (:option sh-basic-offset tab-width sh-indent-after-case 0 sh-indent-for-case-alt '+ sh-indent-for-case-label 0) (:local-set indent-tabs-mode t) (when (executable-find "shfmt") (with-eval-after-load 'apheleia (:option (append apheleia-formatters) '(shfmt . ("shfmt")) (append apheleia-mode-alist) '(sh-mode . shfmt)))) (when (executable-find "shellcheck") (:straight flymake-shellcheck) (:hook flymake-mode flymake-shellcheck-load))) (setup shell-command (:option shell-command-switch (acdw/system ;; I should be testing on some variable (:home "-csi") (:work "-c")) shell-command-prompt-show-cwd t shell-command-default-error-buffer "*shell-command-errors*")) (setup shr (:option shr-width fill-column shr-max-width fill-column shr-max-image-proportion 0.6 shr-image-animate t shr-discard-aria-hidden t shr-folding-mode t)) (setup text (:hook turn-on-auto-fill acdw/setup-fringes)) (setup uniquify (:option uniquify-buffer-name-style 'forward uniquify-separator path-separator uniquify-after-kill-buffer-p t uniquify-ignore-buffers-re "^\\*")) (setup variable-pitch-mode ;; I might want to change this to `buffer-face-mode-hook'... (advice-add 'variable-pitch-mode :after (defun variable-pitch-mode@setup (&rest _) "Set up `variable-pitch-mode' with my customizations." (display-fill-column-indicator-mode (if buffer-face-mode -1 +1))))) (setup view (:option view-read-only t) (defun acdw/read-view-mode () (acdw/reading-mode (if view-mode +1 -1))) (:hook acdw/read-view-mode)) (setup whitespace (:option whitespace-style '(empty indentation space-before-tab space-after-tab) indent-tabs-mode nil tab-width 4 backward-delete-char-untabify-method 'hungry) (:global "M-SPC" cycle-spacing)) (setup windows (require 'acdw-bell) (:option use-dialog-box nil use-file-dialog nil tab-bar-show 1 visible-bell nil ring-bell-function (lambda () (acdw-bell/flash-mode-line (acdw/system :home))) recenter-positions '(top middle bottom)) (tooltip-mode -1)) (setup winner ;; see https://lists.gnu.org/archive/html/emacs-devel/2021-08/msg00888.html (:global "C-x 4 C-/" winner-undo "C-x 4 /" winner-undo "C-x 4 C-?" winner-redo "C-x 4 ?" winner-redo) ;; add `winner-undo' and `winner-redo' to `repeat-mode' (when (fboundp 'repeat-mode) (defvar winner-mode-repeat-map (let ((map (make-sparse-keymap))) (define-key map "/" #'winner-undo) (define-key map "?" #'winner-redo) map) "Keymap to repeat `winner-mode' sequences. Used in `repeat-mode'.") (put 'winner-undo 'repeat-map 'winner-mode-repeat-map) (put 'winner-redo 'repeat-map 'winner-mode-repeat-map)) (winner-mode +1)) (setup windmove (:option windmove-wrap-around t) (:global ;; moving "C-x 4 " windmove-left "C-x 4 " windmove-right "C-x 4 " windmove-up "C-x 4 " windmove-down ;; swapping "C-x 4 S-" windmove-swap-states-left "C-x 4 S-" windmove-swap-states-right "C-x 4 S-" windmove-swap-states-up "C-x 4 S-" windmove-swap-states-down) (when (fboundp 'repeat-mode) (defvar windmove-repeat-map (let ((map (make-sparse-keymap))) ;; moving (define-key map [left] #'windmove-left) (define-key map [right] #'windmove-right) (define-key map [up] #'windmove-up) (define-key map [down] #'windmove-down) ;; swapping (define-key map [S-left] #'windmove-swap-states-left) (define-key map [S-right] #'windmove-swap-states-right) (define-key map [S-up] #'windmove-swap-states-up) (define-key map [S-down] #'windmove-swap-states-down) map) "Keymap to repeat various `windmove' sequences. Used in `repeat-mode'.") (dolist (sym '(windmove-left windmove-right windmove-up windmove-down windmove-swap-states-left windmove-swap-states-right windmove-swap-states-up windmove-swap-states-down)) (put sym 'repeat-map 'windmove-repeat-map)))) (setup w32 (:option w32-allow-system-shell t w32-pass-lwindow-to-system nil w32-lwindow-modifier 'super w32-pass-rwindow-to-system nil w32-rwindow-modifier 'super w32-pass-apps-to-system nil w32-apps-modifier 'hyper)) ;;; "Et cetera" settings ;; This should stay as /minimal/ as possible. Anything that can go somewhere ;; else /should/ go there. (setup emacs (:option attempt-orderly-shutdown-on-fatal-signal nil attempt-stack-overflow-recovery nil echo-keystrokes 0.01 find-function-C-source-directory (acdw/find-emacs-source) kill-read-only-ok t load-prefer-newer t native-comp-async-report-warnings-errors nil set-mark-command-repeat-pop t ) (when (fboundp 'command-completion-default-include-p) (setq read-extended-command-predicate #'command-completion-default-include-p)) (:global "M-=" count-words "C-w" kill-region-or-backward-word "C-c c" capitalize-dwim "C-c u" upcase-dwim "C-c l" downcase-dwim "C-c t" acdw/insert-iso-date) (defalias 'forward-word-with-case 'forward-word "Alias for `forward-word' for use in `case-repeat-map'.") (defalias 'backward-word-with-case 'backward-word "Alias for `backward-word for use in `case-repeat-map'.") (defvar case-repeat-map (let ((map (make-sparse-keymap))) (define-key map "c" #'capitalize-word) (define-key map "u" #'upcase-word) (define-key map "l" #'downcase-word) ;; movement (define-key map "f" #'forward-word-with-case) (define-key map "b" #'backward-word-with-case) map) "A map to repeat word-casing commands. For use with `repeat-mode'.") (dolist (command '(capitalize-word capitalize-dwim upcase-word upcase-dwim downcase-word downcase-dwim forward-word-with-case backward-word-with-case)) (put command 'repeat-map 'case-repeat-map))) ;;; Packages (setup (:straight (0x0 :host gitlab :repo "willvaughn/emacs-0x0")) (:option 0x0-default-server 'ttm)) (setup (:straight (apheleia :host github :repo "raxod502/apheleia")) (apheleia-global-mode +1) ;; Use a dumb formatter on modes that `apheleia' doesn't work for. (add-hook 'before-save-hook (defun before-save@dumb-auto-format () (setq stupid-modes '(makefile-mode org-mode)) ;; If there's no apheleia formatter for the mode, just indent the ;; buffer. (unless (or (apply #'derived-mode-p stupid-modes) (and (fboundp 'apheleia--get-formatter-command) (apheleia--get-formatter-command))) (indent-region (point-min) (point-max)))))) (setup (:straight async) (autoload 'dired-async-mode "dired-async.el" nil t) (dired-async-mode +1)) (setup (:straight ace-link) (ace-link-setup-default) ;; I use C-o here, even though it's usually `open-line', because it still ;; matches the 'o' logic given by abo-abo (it's got the Open mnemonic and ;; it's close to 'l'), and because I've given `open-line' to M-l --- I don't ;; need `downcase-word' much, and when I do, I can use `downcase-dwim', bound ;; to C-c l. (let ((key (kbd "C-o"))) (with-eval-after-load 'erc (autoload 'org-element-lineage "org-element") (define-key erc-mode-map key #'ace-link)) (with-eval-after-load 'org (define-key org-mode-map key #'ace-link-org)) (with-eval-after-load 'gnus (define-key gnus-summary-mode-map key #'ace-link-gnus) (define-key gnus-article-mode-map key #'ace-link-gnus)) (with-eval-after-load 'ert (define-key ert-results-mode-map "o" #'ace-link-help)) ;; And still everything else (setq ace-link-fallback-function #'ace-link-addr) (global-set-key key #'ace-link))) (setup (:straight avy) (:global "C-'" avy-goto-char-timer "M-g f" avy-goto-line "M-g w" avy-goto-word-1 "C-c C-j" avy-resume) (with-eval-after-load "isearch" (define-key isearch-mode-map (kbd "C-'") #'avy-isearch))) (setup (:straight (beginend)) (beginend-global-mode +1)) (setup (:straight (consult :host github :repo "minad/consult")) (require 'acdw-consult) (setq consult--regexp-compiler #'consult--orderless-regexp-compiler) ;; Bindings (:global ;; C-c bindings (`mode-specific-map') ;; I don't use any of these right now. ;; "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 ;; M-g bindings (`goto-map') "M-g e" consult-compile-error "M-g g" consult-goto-line "M-g M-g" consult-goto-line "M-g o" consult-outline "M-g m" consult-mark "M-g k" consult-global-mark "M-g i" consult-imenu "M-g I" consult-project-imenu ;; M-s bindings (`search-map') "M-s g" acdw-consult/sensible-grep "M-s f" acdw-consult/sensible-find "M-s l" consult-line "M-s m" consult-multi-occur "M-s k" consult-keep-lines "M-s u" consult-focus-lines ;; Other bindings "M-y" consult-yank-pop " a" consult-apropos ;; Isearch integration "M-s e" consult-isearch) (:with-map isearch-mode-map (:bind "M-e" consult-isearch "M-s e" consult-isearch "M-s l" consult-line)) ;; see https://github.com/oantolin/completing-history (defmacro consult-history-to-modes (map-hook-alist) (let (defuns) (dolist (map-hook map-hook-alist) (let ((map-name (symbol-name (car map-hook))) (key-defs `(progn (define-key ,(car map-hook) (kbd "M-r") (function consult-history)) (define-key ,(car map-hook) (kbd "M-s") nil)))) (push (if (cdr map-hook) `(add-hook ',(cdr map-hook) (defun ,(intern (concat map-name "@consult-history-bind")) nil ,(concat "Bind `consult-history' to M-r in " map-name ".\n" "Defined by `consult-history-to-modes'.") ,key-defs)) key-defs) defuns))) `(progn ,@ (nreverse defuns)))) (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))) ;; Registers (autoload 'consult-register-preview "consult") (:option register-preview-delay 0 register-preview-function #'consult-register-format) (:advise register-preview :override #'consult-register-window) ;; Xref (:option xref-show-xrefs-function #'consult-xref xref-show-definitions-function #'consult-xref) ;; Projects (:option consult-project-root-function #'vc-root-dir) ;; Competion-at-point (complete-region) (:option completion-in-region-function #'acdw-consult/complete-in-region completion-cycle-threshold 3 tab-always-indent 'complete) ;; Completing-read-multple (if (fboundp #'consult-completing-read-multiple) (:advise completing-read-multple :override #'consult-completing-read-multiple) ;; else (defun crm-indicator (args) (cons (concat "[CRM] " (car args)) (cdr args))) (:advise completing-read-multiple :filter-args #'crm-indicator)) (with-eval-after-loads (vertico consult) (when (boundp 'consult-crm-map) (define-key consult-crm-map "\r" #'+vertico-crm-exit) (define-key consult-crm-map "\t" #'vertico-exit) (defun +vertico-crm-exit () (interactive) (run-at-time 0 nil #'vertico-exit) (funcall #'vertico-exit))))) (setup (:straight crux) (:global "C-x o" acdw/other-window-or-switch-buffer ;; This is about to get WILD! "M-l" crux-smart-open-line "M-o" crux-smart-open-line-above "C-M-\\" crux-cleanup-buffer-or-region "C-x 4 t" crux-transpose-windows) (when (fboundp 'repeat-mode) (unless (boundp 'other-window-repeat-map) (defvar other-window-repeat-map (make-sparse-keymap) "A map for repeating `other-window' keys.")) (define-key other-window-repeat-map "o" #'acdw/other-window-or-switch-buffer) (define-key other-window-repeat-map "O" (defun acdw/other-window-or-switch-buffer-backward () (interactive) (setq repeat-map 'other-window-repeat-map) (acdw/other-window-or-switch-buffer -1))) (put 'acdw/other-window-or-switch-buffer 'repeat-map 'other-window-repeat-map)) (crux-reopen-as-root-mode +1)) ;; requires extension: ;; https://addons.mozilla.org/en-US/firefox/addon/edit-with-emacs1/ (setup (:straight edit-server) (when (and (daemonp) (require 'edit-server nil :noerror)) (edit-server-start) (add-hook 'edit-server-done-hook #'unfill-buffer))) (setup (:straight (electric-cursor :host github :repo "duckwork/electric-cursor")) (electric-cursor-mode +1)) (setup (:straight (elpher :host nil :repo "git://thelambdalab.xyz/elpher.git")) (:option elpher-ipv4-always t elpher-certificate-directory (acdw/dir "elpher/") elpher-gemini-max-fill-width fill-column) (:bind "n" elpher-next-link "p" elpher-prev-link "o" elpher-follow-current-link "G" elpher-go-current) (:hook acdw/reading-mode) (autoload 'elpher-bookmarks "elpher" nil t) (autoload 'elpher-go "elpher" nil t) ;; Make `eww' gemini/gopher aware. From Emacswiki. (define-advice eww-browse-url (:around (fn url &rest args) gemini-elpher) (cond ((string-match-p "\\`\\(gemini\\|gopher\\)://" url) (require 'elpher) (elpher-go url)) (t (apply fn url args)))) (:when-loaded (setup (:straight (gemini-write :host nil :repo "https://alexschroeder.ch/cgit/gemini-write" :branch "main")) (require 'gemini-write)))) (setup (:straight embark) (:global "C-." embark-act) (:option prefix-help-command #'embark-prefix-help-command (append display-buffer-alist) '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none))) embark-prompter #'embark-keymap-prompter embark-verbose-indicator-display-action '(display-buffer-at-bottom (window-height . fit-window-to-buffer))) (setq embark-action-indicator (lambda (map _target) (which-key--show-keymap "Embark" map nil nil 'no-paging) #'which-key--hide-popup-ignore-command) embark-become-indicator embark-action-indicator) (with-eval-after-loads (embark consult) (setup (:straight embark-consult) (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode)))) (setup (:straight epithet) (add-hook 'Info-selection-hook #'epithet-rename-buffer) (add-hook 'eww-after-render-hook #'epithet-rename-buffer) (add-hook 'help-mode-hook #'epithet-rename-buffer) (add-hook 'occur-mode-hook #'epithet-rename-buffer)) (setup (:straight eradio) (:needs "mpv") (:option eradio-player '("mpv" "--no-video" "--no-terminal") eradio-channels `(("KLSU" . "http://130.39.238.143:8010/stream.mp3") ("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") ("WBRH: Jazz & More" . "http://wbrh.streamguys1.com/wbrh-mp3") ("KBRH Blues & Rhythm Hits" . "http://wbrh.streamguys1.com/kbrh-mp3") ("WRKF HD-2" . ,(concat "https://playerservices.streamtheworld.com/" "api/livestream-redirect/WRKFHD2.mp3")) ("WRKF: NPR for the Capital Region" . ,(concat "https://playerservices.streamtheworld.com/" "api/livestream-redirect/WRKFFM.mp3")) ("BadRadio: 24/7 PHONK" . "https://s2.radio.co/s2b2b68744/listen") ("tilderadio" . "https://radio.tildeverse.org/radio/8000/radio.ogg") ("vantaradio" . "https://vantaa.black/radio"))) (:global "C-c r r" eradio-play ; mnemonic: radio "C-c r s" eradio-stop ; mnemonic: stop "C-c r p" eradio-toggle ; mnemonic: play/pause )) (setup (:straight expand-region) (defun acdw/set-mark-or-expand-region (arg) "Set mark at point and activate, jump to mark, or expand region. See `set-mark-command' and `expand-region'. With no prefix argument, either run `set-mark-command' on first invocation and `er/expand-region' on each successive invocation. With any prefix argument (e.g., \\[universal-argument] \\[set-mark-command]), act as with `set-mark-command' (i.e., pop the mark). Don't care about successive invocations." (interactive "P") (cond ((or arg (and set-mark-command-repeat-pop (eq last-command 'pop-to-mark-command))) (setq this-command 'set-mark-command) (set-mark-command arg)) ((eq last-command 'acdw/set-mark-or-expand-region) (er/expand-region 1)) (t (set-mark-command arg)))) (:global "C-=" er/expand-region "C-SPC" acdw/set-mark-or-expand-region)) (setup (:straight fennel-mode) (:needs "fennel") (autoload 'fennel-repl "fennel-mode" nil t) (add-to-list 'auto-mode-alist '("\\.fnl\\'" . fennel-mode))) (setup (:straight gcmh) (:option gcmh-idle-delay 'auto) (gcmh-mode +1)) (setup (:straight geiser)) (setup (:straight (gemini-mode :host nil :repo "https://git.carcosa.net/jmcbray/gemini.el.git")) (add-to-list 'auto-mode-alist '("\\.\\(gemini\\|gmi\\)\\'" . gemini-mode)) (:hook turn-off-auto-fill)) (setup gforth (when (locate-library "gforth") (autoload 'forth-mode "gforth") (add-to-list 'auto-mode-alist '("\\.fs\\'" . forth-mode)) (autoload 'forth-block-mode "gforth") (add-to-list 'auto-mode-alist '("\\.fb\\'" . forth-block-mode)))) (setup (:straight helpful) (:option helpful-max-buffers 5 helpful-switch-buffer-function #'pop-to-buffer ;; helpful-switch-buffer-function ;; (lambda (buf) ;; (pop-to-buffer buf ;; '((display-buffer-reuse-mode-window ;; display-buffer-pop-up-window) ;; (mode . helpful-mode)) ;; :norecord)) ) (:global " f" helpful-callable " v" helpful-variable " k" helpful-key " o" helpful-symbol "C-c C-d" helpful-at-point) (with-eval-after-load 'helpful (define-key helpful-mode-map "q" (defun helpful-mode|quit () (interactive) (bury-buffer) (unless (window-parameter (frame-selected-window) 'no-other-window) (delete-window)))))) (setup (:straight iscroll) (:hook-into text-mode)) (setup (:straight lacarte) (:global "" lacarte-execute-menu-command)) (setup (:straight ledger-mode) (:needs "ledger")) (setup (:straight lua-mode) (add-to-list 'auto-mode-alist '("\\.lua\\'" . lua-mode))) (setup (:straight magit) (:global "C-c g" magit-status) (defun magit-display-buffer-same-window (buffer) "Display BUFFER in the selected window like God intended." (display-buffer buffer '(display-buffer-same-window))) (:option magit-display-buffer-function #'magit-display-buffer-same-window magit-popup-display-buffer-action '((display-buffer-same-window)) magit-refresh-status-buffer nil)) (setup (:straight marginalia) (:option marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light)) (marginalia-mode +1)) (setup (:straight (mastodon :host github :repo "mooseyboots/mastodon.el")) (:straight request) (:option mastodon-instance-url "https://writing.exchange" mastodon-auth-source-file (car auth-sources) mastodon-client--token-file (acdw/dir "mastodon.plstore"))) (setup (:straight (modus-themes :host gitlab :repo "protesilaos/modus-themes")) (:option modus-themes-slanted-constructs t modus-themes-bold-constructs t modus-themes-region 'bg-only modus-themes-org-blocks 'grayscale modus-themes-headings '((1 . section) (t . no-color)) modus-themes-mode-line nil) (acdw/sunrise-sunset #'modus-themes-load-operandi #'modus-themes-load-vivendi)) (setup (:straight mwim) (:global "C-a" mwim-beginning "C-e" mwim-end)) (setup (:straight nov) (:option nov-text-width fill-column) (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))) (setup (:straight package-lint)) (setup (:straight package-lint-flymake)) (setup (:straight olivetti) (:option olivetti-body-width (+ fill-column 4) olivetti-minimum-body-width fill-column) (add-hook 'olivetti-mode-hook (defun acdw/olivetti-mode-hook () (if olivetti-mode (setq-local indicate-empty-lines nil indicate-buffer-boundaries nil) (acdw/setup-fringes))))) (setup (:straight (orderless :host github :repo "oantolin/orderless")) (require 'orderless) (:option (append completion-styles) 'orderless orderless-component-separator #'orderless-escapable-split-on-space orderless-style-dispatchers '(acdw/orderless-dispatch)) (defun fix-dollar (args) (if (string-suffix-p "$" (car args)) (list (concat (substring (car args) 0 -1) "[\x100000-\x10FFFD]*$")) args)) (advice-add #'orderless-regexp :filter-args #'fix-dollar) (defun acdw/orderless-dispatch (pattern _index _total) "My custom dispatcher for `orderless'." (cond ;; Ensure that $ works with Consult commands, which add disambiguation ;; suffixes -- see `fix-dollar' ((string-suffix-p "$" pattern) `(orderless-regexp . ,(concat (substring pattern 0 -1) "[\x100000-\x10FFFD]*$"))) ;; File extensions ((string-match-p "\\`\\.." pattern) `(orderless-regexp . ,(concat "\\." (substring pattern 1) "[\x100000-\x10FFFD]*$"))) ;; Ignore single ! ((string= "!" pattern) `(orderless-literal . "")) ;; Character folding ((string-prefix-p "%" pattern) `(char-fold-to-regexp . ,(substring pattern 1))) ((string-suffix-p "%" pattern) `(char-fold-to-regexp . ,(substring pattern 0 -1))) ;; Without literal ((string-prefix-p "!" pattern) `(orderless-without-literal . ,(substring pattern 1))) ((string-suffix-p "!" pattern) `(orderless-without-literal . ,(substring pattern 0 -1))) ;; Initialism matching ((string-prefix-p "`" pattern) `(orderless-initialism . ,(substring pattern 1))) ((string-suffix-p "`" pattern) `(orderless-initialism . ,(substring pattern 0 -1))) ;; Literal matching ((string-prefix-p "=" pattern) `(orderless-literal . ,(substring pattern 1))) ((string-suffix-p "=" pattern) `(orderless-literal . ,(substring pattern 0 -1))) ;; Flex matching ((string-prefix-p "~" pattern) `(orderless-flex . ,(substring pattern 1))) ((string-suffix-p "~" pattern) `(orderless-flex . ,(substring pattern 0 -1)))))) (setup (:straight org org-contrib) (require 'acdw-org) ; so I don't clutter up init.el (:option org-adapt-indentation nil org-agenda-files nil ; only until I set this up org-catch-invisible-edits 'smart org-clock-clocked-in-display 'mode-line org-clock-string-limit 7 ; gives time and not title org-confirm-babel-evaluate nil org-directory "~/org" org-ellipsis " …" 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 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 '(300) org-imenu-depth 3 org-outline-path-complete-in-steps nil org-pretty-entities t 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 0 ; (- 0 fill-column -3) ) (:bind "RET" acdw-org/return-dwim "" acdw-org/org-table-copy-down "M-SPC M-SPC" insert-zero-width-space "C-c C-l" org-insert-link-dwim "M-w" acdw/copy-region-plain "C-c C-n" acdw/org-next-heading-widen "C-c C-p" acdw/org-previous-heading-widen) (with-eval-after-load 'org-export (add-to-list 'org-export-filter-final-output-functions #'org-export-remove-zero-width-spaces)) (defun acdw/org-fix-lines-before-save () (add-hook 'before-save-hook #'acdw-org/fix-blank-lines-in-buffer 0 :local)) (:hook variable-pitch-mode olivetti-mode acdw/org-fix-lines-before-save) (advice-add 'org-delete-backward-char :override #'acdw-org/delete-backward-char) (add-hook 'org-mode-hook (defun org-mode@wc-stupid () (unless (and wc-mode (> 0 (+ (or wc-orig-words 0) (or wc-words-delta 0))))) (setq-local wc-count-words-function (lambda (start end) "Count words stupidly with a limit." (acdw-org/count-words-stupidly start end 999))))) (setup (:straight org-appear) (:hook-into org-mode))) (setup (:straight page-break-lines) (global-page-break-lines-mode +1)) (setup (:straight paredit) ;; I don't use paredit-splice-sexp much, and it stomps on isearch. (:unbind "M-s") (defun paredit@setup () "Correct weirdnesses and set up paredit mode." (:with-map lisp-mode-shared-map (:bind "DEL" paredit-backward-delete "C-M-;" comment-or-uncomment-sexp "C-" paredit-backward-kill-word)) (paredit-mode +1)) (dolist (mode lispy-modes) (add-hook (intern (concat (symbol-name mode) "-hook")) #'paredit@setup)) (require 'eldoc) (eldoc-add-command 'paredit-backward-delete 'paredit-close-round)) (setup (:straight paren-face) (dolist (mode lispy-modes) (add-hook (intern (concat (symbol-name mode) "-hook")) #'paren-face-mode))) (setup (:straight restart-emacs) (defun emacs-upgrade (&optional update-packages) "Pull config, upgrade packages, restart Emacs." (interactive "P") (when update-packages (straight-pull-all)) (emacs-git-pull-config) (restart-emacs))) (setup (:straight simple-modeline) (setup (:straight minions)) (require 'acdw-modeline) (:option simple-modeline-segments '((acdw-modeline/modified acdw-modeline/buffer-name acdw-modeline/vc-branch acdw-modeline/position) (simple-modeline-segment-misc-info acdw-modeline/erc acdw-modeline/wc acdw-modeline/text-scale simple-modeline-segment-process acdw-modeline/god-mode-indicator acdw-modeline/winum acdw-modeline/minions acdw-modeline/narrowed simple-modeline-segment-major-mode))) ;; I've put in a pull request to add the (- 0 right-margin) bit here. (advice-add 'simple-modeline--format :override (defun simple-modeline@format (lefts rights) (let* ((left (simple-modeline--format-segments lefts)) (right (simple-modeline--format-segments rights)) (reserve (length right))) (concat left (propertize " " 'display `((space :align-to (- right (- 0 right-margin) ,reserve))) 'face '(:inherit simple-modeline-space)) right)))) (simple-modeline-mode +1)) (setup (:straight sly) (defvar acdw/lisp-bin (or (executable-find "sbcl") (executable-find "clisp") "")) (:needs acdw/lisp-bin) (:option inferior-lisp-program acdw/lisp-bin sly-kill-without-query-p t) (:also-load sly-autoloads) (setup (:straight clhs)) (:with-feature sly-mrepl (dolist (key '("RET" "")) (:bind key sly-mrepl-return-at-end)) (:bind "C-c C-c" sly-mrepl-return)) (defun sly-mrepl-return-at-end () (interactive) (if (<= (point-max) (point)) (sly-mrepl-return) (if (bound-and-true-p paredit-mode) (paredit-newline) (electric-newline-and-maybe-indent))))) (setup (:straight ssh-config-mode) (dolist (spec '(("/\\.ssh/config\\'" . ssh-config-mode) ("/sshd?_config\\'" . ssh-config-mode) ("/knownhosts\\'" . ssh-known-hosts-mode) ("/authorized_keys2?\\'" . ssh-authorized-keys-mode))) (add-to-list 'auto-mode-alist spec)) (add-hook 'ssh-config-mode-hook #'turn-on-font-lock)) (setup (:straight (topsy :host github :repo "alphapapa/topsy.el")) (:hook-into prog-mode)) (setup (:straight typo) ;; jlf & cvandusen on #emacs make a great point: ’ (RIGHT SINGLE QUOTATION ;; MARK) is /not/ an apostrophe. Making it curly is a typographical ;; consideration, not an input consideration. (I suppose you could make the ;; argument that all of these are typographical considerations, but .. bleh.) (define-typo-cycle typo-cycle-apostrophe "Cycle through apostrophe-like graphemes. If used with a numeric prefix argument N, N apostrophes will be inserted." ("'" "′" "″" "’")) (define-typo-cycle typo-cycle-backtick "Cycle through backtick and left single quotation mark. If used with a numeric prefix argument N, N backticks will be inserted." ("`" "‘")) (:bind "'" typo-cycle-apostrophe "`" typo-cycle-backtick) ;; Enable C-c 8 map in all buffers (typo-global-mode +1) (add-hook 'text-mode-hook (defun text-mode@typo-unless () "Start `typo-mode' UNLESS the buffer matches a predicate." ;; I implement this instead of using ;; `typo-disable-electricity-functions' because the latter checks ;; on every pertinent keypress. I know I want /no/ typo-ing in ;; these certain buffers, so I won't even turn on the mode. (unless (or ; predicates here (string-match-p "COMMIT_EDITMSG" (or (buffer-name) ""))) (typo-mode +1))))) (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 (acdw/dir "undo/" t) undo-fu-session-compression (acdw/system :home)) (global-undo-fu-session-mode +1)) (setup (:straight unfill)) (setup (:straight (unfocused :host github :repo "duckwork/unfocused")) (unfocused-mode +1)) (setup (:straight (vertico :host github :repo "minad/vertico" :files ("*" "extensions/*" (:exclude ".git")))) (:option resize-mini-windows 'grow-only vertico-count-format nil vertico-cycle t) (defun up-directory (arg) "Move up a directory (delete backwards to /)." (interactive "p") (if (string-match-p "/." (minibuffer-contents)) (zap-up-to-char (- arg) ?/) (backward-kill-word arg))) (with-eval-after-load 'vertico (define-key vertico-map (kbd "") #'up-directory)) (if (boundp 'comp-deferred-compilation-deny-list) (add-to-list 'comp-deferred-compilation-deny-list "vertico")) (vertico-mode +1) ;; Extensions! (:also-load vertico-mouse) (vertico-mouse-mode +1) ;; Prefix the current candidate with "> ". From Vertico wiki. (defun vertico-format@add-arrow (orig cand prefix suffix index _start) (setq cand (funcall orig cand prefix suffix index _start)) (concat (if (= vertico--index index) (propertize "> " 'face 'vertico-current) " ") cand)) (advice-add #'vertico--format-candidate :around #'vertico-format@add-arrow)) (setup (:straight wc-mode) ; TODO: move some of this stuff around (:option wc-modeline-format "[%tww]" wc-idle-wait 2) (:hook-into text-mode) (defun acdw-modeline/wc () "Display current `wc-buffer-stats'." (when (bound-and-true-p wc-mode) (or wc-buffer-stats "[w]")))) (setup (:straight web-mode) (:option css-level-offset 2 js-indent-level 2 sgml-indent-offset 2) (dolist (ext '("\\.\\(p\\|dj\\)?html\\'" "\\.html?\\'" "\\.\\(tpl\\.\\)?php\\'" "\\.[agj]sp\\'" "\\.as[cp]x\\'" "\\.erb\\'" "\\.mustache\\'")) (add-to-list 'auto-mode-alist `(,ext . web-mode)))) (setup (:straight which-key) (:option which-key-show-early-on-C-h t which-key-idle-delay 1 which-key-idle-secondary-delay 0.5) (which-key-setup-side-window-right-bottom) (which-key-mode +1)) (setup (:straight whitespace-cleanup-mode) (global-whitespace-cleanup-mode +1)) (setup (:straight winum) (:option winum-scope 'frame-local winum-auto-setup-mode-line nil winum-ignored-buffers '(" *which-key*") winum-format " %s") (when-unfocused winum-map-keys (defvar winum--keys-mapped nil "Whether `winum' keys have been mapped already.") (when (and (not winum--keys-mapped) (display-graphic-p)) (:with-map winum-keymap (:bind "M-0" winum-select-window-0-or-10 "M-1" winum-select-window-1 "M-2" winum-select-window-2 "M-3" winum-select-window-3 "M-4" winum-select-window-4 "M-5" winum-select-window-5 "M-6" winum-select-window-6 "M-7" winum-select-window-7 "M-8" winum-select-window-8 "M-9" winum-select-window-9)) (setq winum--keys-mapped t))) (winum-mode +1)) (setup (:straight xr)) (setup (:straight zzz-to-char) (defun acdw/zzz-up-to-char (prefix) "Call `zzz-up-to-char', unless issued a PREFIX, in which case call `zzz-to-char'." (interactive "P") (if prefix (call-interactively #'zzz-to-char) (call-interactively #'zzz-up-to-char))) (:global "M-z" acdw/zzz-up-to-char)) ;;; System-dependent ;;;; Home (when (acdw/system :home) (setup (:straight exec-path-from-shell) (when (daemonp) (exec-path-from-shell-initialize))) (setup (:straight pkgbuild-mode)) (setup (:straight (pdf-tools :host github :repo "vedang/pdf-tools")) (add-to-list 'auto-mode-alist '("\\.pdf\\'" . pdf-view-mode)) (pdf-loader-install)) (setup (:straight systemd)) (setup (:straight vterm)) (add-hook 'after-make-frame-functions (defun after-make-frame@maximize (frame) (unless (or edit-server-edit-mode) (toggle-frame-maximized frame))))) ;;;; Work (when (acdw/system :work) (setup (:straight ahk-mode))) ;;; init.el ends here