;;; 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/~zge/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 (require 'acdw-private) ;;;; Utility functions and variables ;; see also: `acdw' and friends. Functions here aren't big enough, or they're ;; too tightly bound to stuff here, to be placed in `acdw'. (defvar lispy-modes '(emacs-lisp-mode eval-expression-minibuffer ielm-mode lisp-mode lisp-interaction-mode scheme-mode slime-repl-mode sly-mrepl-mode) "List of modes that are lisp-like enough to hook packages into.") ;;; 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 (global-auto-revert-mode +1)) (setup browse-url (when (acdw/system :work) (add-to-list 'exec-path "C:/Program Files/Mozilla Firefox")) (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) ;; `browse-url-browser-function' as an alist is deprecated in Emacs 28 for ;; `browse-url-handlers'. (set-default (if (version<= emacs-version "28") 'browse-url-browser-function 'browse-url-handlers) '(("." . eww-browse-url))) ;; Buttonize gemini:// links. (acdw/add-button-url-regexp-protocol "gemini")) (setup buffers (:global "C-x k" acdw/kill-a-buffer)) (setup completion (:option completion-ignore-case t read-buffer-completion-ignore-case t completion-styles '(partial-completion substring flex) 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 (acdw/dir "custom.el") custom-magic-show nil custom-magic-show-button t custom-unlispify-tag-names nil custom-variable-default-form 'edit)) (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) (:also-load dired-x) (: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) (:option (prepend dired-open-functions) #'dired-open-xdg))) (:when-loaded (: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 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)))) (defun acdw/eval-region-or-buffer () (interactive) (if (region-active-p) (let ((begin (region-beginning)) (end (region-end))) (with-message (format "Evaluating %S -> %S" begin end) (eval-region begin end))) (with-message "Evaluating buffer" (eval-buffer)))) ;; 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 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-header-line-face-method #'erc/update-header-line-show-disconnected erc-hide-list '("JOIN" "NICK" "PART" "QUIT" "MODE" "324" "329" "332" "333" "353" "477") erc-interpret-controls-p t erc-interpret-mirc-color t erc-join-buffer 'buffer erc-kill-buffer-on-part t erc-kill-queries-on-quit t erc-kill-server-buffer-on-quit t erc-nick "acdw" erc-prompt (lambda () (concat (buffer-name) ">")) erc-prompt-for-password nil ; use ~/.authinfo erc/servers (mapcar #'car erc-autojoin-channels-alist) erc-server-coding-system '(utf-8 . utf-8) erc-track-exclude-types erc-hide-list erc-track-position-in-mode-line 'before-modes) ;; 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)))) (:hook erc-autoaway-mode erc-track-mode erc-truncate-mode) (setup (:straight erc-hl-nicks) (with-eval-after-load 'erc (require 'erc-hl-nicks) (:option erc-hl-nicks-minimum-contrast-ratio 4.5) (:hook-into erc-mode)))) (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)) (function (cdr spec)))) ;; Etc. (when (boundp 'simple-modeline--mode-line) (setq mode-line-format '(:eval simple-modeline--mode-line)))) (: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 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 (setq-default ispell-program-name "hunspell" ispell-dictionary "en_US" ispell-personal-dictionary "~/.hunspell_personal") (: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)) (defun flyspell-start () "Start `flyspell-mode' or `flyspell-prog-mode', depending on current mode." (interactive) (cond ((derived-mode-p 'text-mode) (flyspell-mode)) ((derived-mode-p 'prog-mode) (flyspell-prog-mode)) ((called-interactively-p) (message "Non-text or -prog mode. Run `flyspell-mode'.")))) (when (acdw/system :home) (add-hook 'change-major-mode-hook #'flyspell-start)) (:when-loaded (setup (:straight flyspell-correct) (define-key flyspell-mode-map (kbd "C-;") #'flyspell-correct-wrapper)))) (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 ibuffer (: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 "C-x C-b" ibuffer) (defun ibuffer-filter-to-default () (ibuffer-switch-to-saved-filter-groups "default")) (:hook ibuffer-filter-to-default) (:also-load ibuf-ext) (: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 minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt) enable-recursive-minibuffers t file-name-shadow-properties '(invisible t intangible t) read-answer-short t) (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 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) (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 "C-M-%" 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-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 (:only-if (fboundp #'repeat-mode)) (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)) (:option (append 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 0 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 (:hook acdw-fonts/adapt-variable-pitch)) (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) (winner-mode +1)) (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 disabled-command-function nil load-prefer-newer t native-comp-async-report-warnings-errors nil echo-keystrokes 0.01 attempt-stack-overflow-recovery nil attempt-orderly-shutdown-on-fatal-signal nil find-function-C-source-directory (acdw/find-emacs-source)) (:global "M-=" count-words "C-w" kill-region-or-backward-word " h" nil ; HELLO takes a long time to load on Windows ) (when (display-graphic-p) (:global "" keyboard-escape-quit)) ;; Remap C-h to DEL -- can be the "help" key (define-key key-translation-map [?\C-h] [?\C-?]) (:global "C-c t" acdw/insert-iso-date "C-z" nil)) ;;; Packages (setup (:straight (0x0 :host nil :repo "https://git.sr.ht/~zge/nullpointer-emacs")) (:option 0x0-default-host '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)) (setup (:straight avy) (:global "C-:" avy-goto-char "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")) ;; "Sensible" functions (defun consult-sensible-grep (&optional arg) "Perform `consult-git-grep' if in a git project, otherwise `consult-ripgrep' if ripgrep is installed, otherwise `consult-grep'." (interactive "P") (cond ((string-equal (vc-backend buffer-file-name) "Git") (call-interactively #'consult-git-grep)) ((executable-find "rg") (call-interactively #'consult-ripgrep)) (t (call-interactively #'consult-grep)))) (defun consult-sensible-find (&optional arg) "Peform `consult-locate' if locate is installed, otehrwise `consult-find'." (interactive "P") (cond ((executable-find "locate") (call-interactively #'consult-locate)) (t (call-interactively #'consult-find)))) ;; 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" consult-sensible-grep "M-s f" 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)) ;; 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) (:global "M-/" completion-at-point) (:option completion-in-region-function #'consult-completion-in-region completion-cycle-threshold 3 tab-always-indent 'complete)) (setup (:straight crux) (:global "M-`" crux-other-window-or-switch-buffer "C-o" 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)) (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 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) (:option eradio-player '("mpv" "--no-video" "--no-terminal") eradio-channels '(("tilderadio" . "https://radio.tildeverse.org/radio/8000/radio.ogg")))) (setup (:straight expand-region) (:global "C-=" er/expand-region)) (setup (:straight fennel-mode) (:needs "fennel") (autoload 'fennel-repl "fennel-mode" nil t) (:file-match "\\.fnl\\'")) (setup (:straight form-feed) (global-form-feed-mode +1)) (setup (:straight geiser)) (setup (:straight (gemini-mode :host nil :repo "https://git.carcosa.net/jmcbray/gemini.el.git")) (:file-match "\\.\\(gemini\\|gmi\\)\\'") (: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) (:global " f" helpful-callable " v" helpful-variable " k" helpful-key " o" helpful-symbol "C-c C-d" helpful-at-point)) (setup (:straight imenu-anywhere) (:global "C-." imenu-anywhere)) (setup (:straight iscroll) (:hook-into text-mode)) (setup (:straight ledger-mode) (:needs "ledger")) (setup (:straight lua-mode) (:file-match "\\.lua\\'")) (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")) (: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) (:file-match "\\.epub\\'")) (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 (prepend completion-styles) 'orderless)) (setup (:straight (org :host nil :repo "https://code.orgmode.org/bzg/org-mode.git")) (:straight (org-contrib :host nil :repo "https://git.sr.ht/~bzg/org-contrib")) (require 'acdw-org) ; so I don't clutter up init.el (:option org-adapt-indentation nil org-catch-invisible-edits 'smart org-confirm-babel-evaluate nil 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-imenu-depth 3 org-pretty-entities t 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-tags-column (- 0 fill-column -3) org-directory "~/org") (: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) (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 acdw/org-fix-lines-before-save) (advice-add 'org-delete-backward-char :override #'acdw-org/delete-backward-char)) (setup (:straight paredit) ;; I don't use paredit-splice-sexp much, and it stomps on isearch. (:unbind "M-s") (defun setup-paredit-mode () "Correct weirdnesses and set up paredit mode." (paredit-mode +1) (let ((map lisp-mode-shared-map)) (define-key map (kbd "DEL") #'paredit-backward-delete) (define-key map (kbd "C-M-;") #'comment-or-uncomment-sexp))) (dolist (mode lispy-modes) (add-hook (intern (concat (symbol-name mode) "-hook")) #'setup-paredit-mode)) (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)) (:option ;; `acdw-org/count-words' is too slow to use in the modeline. ;; (prepend acdw-modeline/word-count-modes) '(org-mode . acdw-org/count-words) simple-modeline-segments '((acdw-modeline/modified acdw-modeline/buffer-name acdw-modeline/vc-branch simple-modeline-segment-position) (simple-modeline-segment-misc-info acdw-modeline/erc acdw-modeline/word-count acdw-modeline/text-scale simple-modeline-segment-process acdw-modeline/god-mode-indicator acdw-modeline/winum acdw-modeline/minions simple-modeline-segment-major-mode))) (require 'acdw-modeline) (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 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 (unfocused :host github :repo "duckwork/unfocused")) (unfocused-mode +1)) (setup (:straight (vertico :host github :repo "minad/vertico")) (setq resize-mini-windows 'grow-only) (if (boundp 'comp-deferred-compilation-deny-list) (add-to-list 'comp-deferred-compilation-deny-list "vertico")) (vertico-mode +1)) (setup (:straight vuiet) (:needs "youtube-dl" "mpv") ;; lastfm.el is required too, and needs some setup: ;; https://github.com/mihaiolteanu/lastfm.el (setup (:straight lastfm) (require 'lastfm))) (setup (:straight web-mode) (:option css-level-offset 2 js-indent-level 2 sgml-indent-offset 2) (:file-match "\\.\\(p\\|dj\\)?html\\'" "\\.html?\\'" "\\.\\(tpl\\.\\)?php\\'" "\\.[agj]sp\\'" "\\.as[cp]x\\'" "\\.erb\\'" "\\.mustache\\'")) (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 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 vterm)))