;;; emacs init --- an init for emacs -*- lexical-binding: t; -*- ;; by C. Duckworth ;; URL: https://git.acdw.net/emacs ;; Bankruptcy: 9 ;; ;; 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. (yoke +emacs (require* '+emacs '+window '+lisp) ;; Settings (setf truncate-string-ellipsis "…" ring-bell-function #'ignore read-file-name-completion-ignore-case t comment-auto-fill-only-comments t password-cache t eww-use-browse-url "." ; use `browse-url' in every link password-cache-expiry (* 60 60) initial-buffer-choice (defun +initial-buffer-choose () (cond ((equal (get-buffer "*Messages*") (other-buffer)) (get-buffer "*scratch*")) (:else (other-buffer))))) ;; "Safe" variables (dolist (var+pred '((browse-url-browser-function ;; All types defined by custom are safe. . (lambda (f) ;; Whooooo boy (memq f (mapcar (lambda (i) (plist-get (cdr i) :value)) (seq-filter (lambda (i) (eq (car i) 'function-item)) (cdr (get 'browse-url-browser-function 'custom-type))))))))) (put (car var+pred) 'safe-local-variable (cdr var+pred))) ;; Keys (define-key* (current-global-map) "C-x C-k" #'kill-current-buffer "C-/" #'undo-only "C-?" #'undo-redo "C-x C-c" (defun delete-frame-or-quit (arg) (interactive "P") (cond (arg (delete-frame nil :force)) ((= 1 (length (frame-list))) (and (yes-or-no-p "Kill emacs? ") (save-buffers-kill-emacs t))) (:else (delete-frame)))) "C-x r q" (defun really-quit-emacs (arg) (interactive "P") (cond (arg (save-buffers-kill-emacs t)) (:else (save-buffers-kill-terminal t)))) "M-SPC" #'+cycle-spacing ;; "M-/" #'hippie-expand ; `hippie-completing-read' "M-=" #'count-words "C-x C-b" #'ibuffer "C-x 4 n" #'clone-buffer "S-" #'mouse-set-mark "C-x 0" #'+delete-window-or-bury-buffer ;; "M-j" nil ; `avy' "" nil "C-z" nil "M-o" #'other-window|switch-buffer "C-M-;" #'+lisp-comment-or-uncomment-sexp "C-x 5 z" #'suspend-frame "M-@" #'dictionary-search "C-x f" #'find-file) (define-key* text-mode-map "C-M-k" #'kill-paragraph "C-o" (defun open-paragraph (&optional arg) "Open a paragraph after paragraph at point. A paragraph is defined as continguous non-empty lines of text surrounded by empty lines, so opening a paragraph means to make three blank lines, then place the point on the second one. Called with prefix ARG, open a paragraph before point." ;; TODO: Take an integer as ARG, allowing for skipping paragraphs up and down. (interactive "*P") ;; Go to next blank line. This /isn't/ `end-of-paragraph-text' because ;; that's weird with org, and I'm guessing other modes too. (unless (looking-at "^$") (forward-line (if arg -1 +1))) (while (and (not (looking-at "^$")) (= 0 (forward-line (if arg -1 +1))))) (newline) (when arg (newline) (forward-line -2)) (delete-blank-lines) (newline 2) (previous-line))) ;; Hooks (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) (add-hook 'find-file-not-found-functions #'+auto-create-missing-dirs) (add-hook 'text-mode-hook #'abbrev-mode) (add-hook 'find-file-hook #'+vc-off-when-remote) (add-hook 'prog-mode-hook #'auto-fill-mode) ;; Advice (add-function :after after-focus-change-function #'+save-some-buffers-debounce) (define-advice keyboard-escape-quit (:around (fn &rest r) keep-window-open) "Don't close quits on `keyboard-escape-quit'." (let ((buffer-quit-function #'ignore)) (apply fn r))) ;; Faces (set-face-attribute 'default nil :family "Comic Code" :height 100) (set-face-attribute 'bold nil :family "Comic Code" :weight 'bold) (set-face-attribute 'variable-pitch nil :family "Comic Code") ;; Modes (winner-mode)) (yoke custom ; This is `cus-edit' but meh (require '+custom) (setf custom-file (private/ "custom.el")) (add-to-list* '+custom-allowed-variables 'safe-local-variable-values 'warning-suppress-types 'ispell-buffer-session-localwords) (eval-after init (+custom-load-some-customizations :noerror))) (yoke modus-themes (setf modus-themes-bold-constructs t modus-themes-italic-constructs t modus-themes-headings '((1 monochrome bold italic) (2 monochrome bold) (3 monochrom italic) (t monochrome))) (cond ((require 'dawn nil :noerrer) (add-hook* '+custom-after-load-hook (defun dawn@custom () (load-theme 'modus-operandi :noconfirm :noenable) (load-theme 'modus-vivendi :noconfirm :noenable) (dawn-schedule #'modus-themes-load-operandi #'modus-themes-load-vivendi)))) (:else (load-theme 'modus-operandi)))) (yoke time (setf display-time-mail-function (defun +notmuch-new-mail-p () (plist-get (cl-find "inbox+unread" (ignore-errors (notmuch-hello-query-counts notmuch-saved-searches)) :key (lambda (l) (plist-get l :name)) :test #'equal) :count)) display-time-use-mail-icon t read-mail-command #'+notmuch-goto display-time-format " %a %-e, %H:%M" ;; `display-time-format' makes these unnecessary, but I'll keep em display-time-24hr-format t display-time-day-and-date t display-time-default-load-average nil) (display-time-mode)) (yoke pita (require 'pita) (advice-add 'indent-region :before #'with-region-or-buffer)) (yoke (undo-fu-session "https://codeberg.org/ideasman42/emacs-undo-fu-session") (setf undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'") undo-fu-session-directory (.etc "undo/" t) undo-fu-session-compression (cond ((executable-find "gzip") 'gz) ((executable-find "bzip2") 'bz2) ((executable-find "xz") 'xz) (t nil))) (global-undo-fu-session-mode)) (yoke whitespace (setf whitespace-line-column nil whitespace-style '( face trailing tabs tab-mark indentation space-after-tab space-before-tab)) (defun +whitespace-mode-for-writable-buffers () "Turn on `whitespace-mode' if the buffer is writable, off otherwise." (whitespace-mode (if buffer-read-only -1 t))) (add-hook* '(text-mode-hook prog-mode-hook read-only-mode-hook) #'+whitespace-mode-for-writable-buffers) (add-hook 'before-save-hook #'whitespace-cleanup) (define-advice whitespace-cleanup (:around (fn &rest r) preserve-point) (let ((col (current-column))) (apply fn r) (move-to-column col t) (set-buffer-modified-p nil)))) (yoke elisp-mode (setf eval-expression-print-length nil ; remove ellipses from `eval-expression' eval-expression-print-level nil) (define-key* '(emacs-lisp-mode-map lisp-interaction-mode-map) "C-c C-c" #'eval-defun "C-c C-k" (defun +elisp-eval-region-or-buffer () (interactive) (cond ((region-active-p) (eval-region (region-beginning) (region-end)) (message "Region evaluated.")) (t (eval-buffer) (message "Buffer %s evaluated." (buffer-name))))) "C-c C-z" #'ielm) (define-advice eval-region (:around (fn beg end &rest args) pulse) (apply fn beg end args) (pulse-momentary-highlight-region beg end))) (yoke isearch (define-key* (current-global-map) "C-s" #'isearch-forward-regexp "C-r" #'isearch-backward-regexp "C-M-s" #'isearch-forward "C-M-r" #'isearch-backward)) (yoke ispell (require* '+ispell 'ispell) (add-hook 'before-save-hook #'+ispell-move-buffer-words-to-dir-locals-hook) (setf ispell-program-name (or (executable-find "ispell") (executable-find "aspell"))) (put 'ispell-buffer-session-localwords 'safe-local-variable #'+ispell-safe-local-p)) (yoke 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) (setf 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))) (yoke dired (require 'dired-x) (setf 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) (setq-local-hook dired-mode-hook truncate-lines t) (define-key* (current-global-map) "C-x C-j" #'dired-jump [remap list-directory] #'dired) (eval-after dired (define-key* dired-mode-map "" #'dired-up-directory "C-j" #'dired-up-directory)) (add-hook* 'dired-mode-hook #'dired-hide-details-mode #'hl-line-mode)) (yoke (dired-hacks "https://github.com/Fuco1/dired-hacks") (define-key* dired-mode-map "TAB" #'dired-subtree-sycle "i" #'dired-subtree-toggle) (add-hook* 'dired-mode-hook #'dired-collapse-mode)) (yoke auth-source (setf auth-sources `(default "secrets:passwords")) (setq-local-hook authinfo-mode-hook truncate-lines t)) (yoke (consult "https://github.com/minad/consult") (require 'consult) (setf register-preview-delay 0 register-preview-function #'consult-register-format xref-show-xrefs-function #'consult-xref tab-always-indent 'complete completion-in-region-function #'consult-completion-in-region consult-narrow-key "<" consult--regexp-compiler #'consult--default-regexp-compiler) (advice-add #'register-preview :override #'consult-register-window) (define-key* (current-global-map) ;; Etc "M-S-x" #'consult-mode-command ;; C-c bindings (mode-specific-map) "C-c h" #'consult-history "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 M-i" #'consult-imenu "M-g I" #'consult-imenu-multi ;; M-s bindings (search-map) "M-s f" #'consult-find "M-s F" #'consult-locate "M-s g" #'consult-grep "M-s G" #'consult-git-grep "M-s r" #'consult-ripgrep "M-s l" #'consult-line "M-s L" #'consult-line-multi "M-s m" #'consult-multi-occur "M-s k" #'consult-keep-lines "M-s u" #'consult-focus-lines ;; Isearch integration "M-s e" #'consult-isearch-history) (eval-after isearch-mode (define-key* isearch-mode-map "M-e" #'consult-isearch-history "M-s e" #'consult-isearch-history "M-s l" #'consult-line "M-s L" #'consult-line-multi)) (eval-after org (define-key org-mode-map (kbd "M-g o") #'consult-org-heading)) (eval-after consult-imenu (setf (alist-get ?y (plist-get (alist-get 'emacs-lisp-mode consult-imenu-config) :types)) '("Yoke")))) (yoke (orderless "https://github.com/oantolin/orderless") (require 'orderless) (setf completion-styles '(substring orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles basic partial-completion))) orderless-component-separator #'orderless-escapable-split-on-space)) (yoke (vertico "https://github.com/minad/vertico") (require 'vertico) (setf resize-mini-windows 'grow-only vertico-count-format nil vertico-cycle t) (vertico-mode)) (yoke (embark "https://github.com/oantolin/embark") (require 'embark) (setf prefix-help-command #'embark-prefix-help-command embar-keymap-prompter-key ";") (define-key* (list (current-global-map) 'minibuffer-local-map) "C-." #'embark-act "M-." #'embark-dwim " B" #'embark-bindings) (define-key* embark-file-map "l" #'vlf) (eval-after (embark consult) (require 'embark-consult) (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode))) (yoke (marginalia "https://github.com/minad/marginalia/") (marginalia-mode)) (yoke (wgrep "https://github.com/mhayashi1120/Emacs-wgrep") (require 'wgrep) (define-key* grep-mode-map "C-x C-q" #'wgrep-change-to-wgrep-mode)) (yoke (slime "https://github.com/slime/slime") :when (executable-find "sbcl") (setf inferior-lisp-program (executable-find "sbcl")) (eval-after slime (setf slime-completion-at-point-functions (delq 'slime-c-p-c-completion-at-point slime-completion-at-point-functions)))) (yoke (puni "https://github.com/amaikinono/puni") (define-key* puni-mode-map "C-)" #'puni-slurp-forward "C-(" #'puni-slurp-backward "C-}" #'puni-barf-forward "C-{" #'puni-barf-backward "M-(" (defun +puni-open-then-slurp-forward (&optional n) (interactive "p") (insert "()") (backward-char) (puni-slurp-forward n))) (electric-pair-mode) (add-hook* '(prog-mode-hook ielm-mode-hook lisp-interaction-mode-hook lisp-mode-hook scheme-mode-hook) #'puni-mode)) (yoke (hungry-delete "https://github.com/nflath/hungry-delete") (setq hungry-delete-chars-to-skip " \t" hungry-delete-join-reluctantly nil) (eval-after hungry-delete (add-to-list* 'hungry-delete-except-modes #'eshell-mode #'nim-mode #'python-mode)) (defun +hungry-delete-or (hd-fn fn arg) (funcall (if (looking-back (format "[%s]" hungry-delete-chars-to-skip) arg) hd-fn fn) arg)) (define-key* puni-mode-map [remap puni-backward-delete-char] (defun puni@hungry-delete-backward (arg) (interactive "p") (+hungry-delete-or #'hungry-delete-backward #'puni-backward-delete-char arg)) [remap puni-forward-delete-char] (defun puni@hungry-delete-forward (arg) (interactive "p") (+hungry-delete-or #'hungry-delete-forward #'puni-forward-delete-char arg))) (global-hungry-delete-mode)) (yoke (cape "https://github.com/minad/cape") ;; Insinuate in a lot of modes (defvar +capes '(cape-file cape-dabbrev)) (defun +cape-insinuate (hook capf &optional capes) "Insinuate CAPES into a HOOK along with CAPF function. CAPES defaults to `+capes'. CAPF will be made un-exclusive." (setq-local-hook hook completion-at-point-functions (apply #'list (cape-capf-properties capf :exclusive 'no) (or capes +capes)))) (+cape-insinuate 'emacs-lisp-mode-hook #'elisp-completion-at-point)) (yoke (minions "https://github.com/tarsius/minions") (minions-mode)) (yoke (magit "https://github.com/magit/magit" :load "lisp") :depends ((transient "https://github.com/magit/transient" :load "lisp") (dash "https://github.com/magnars/dash.el") (with-editor "https://github.com/magit/with-editor" :load "lisp")) (autoload #'transient--with-suspended-override "transient") (autoload #'magit "magit" nil :interactive) (define-key* (current-global-map) "C-x g" #'magit)) (yoke (git-modes "https://github.com/magit/git-modes") (require 'git-modes)) (yoke (visual-fill-column "https://codeberg.org/joostkremers/visual-fill-column") (setq visual-fill-column-center-text t) (add-hook* 'visual-fill-column-mode-hook #'visual-line-mode) (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) (yoke (org "https://git.savannah.gnu.org/git/emacs/org-mode.git" :load "lisp") :depends ((org-contrib "https://git.sr.ht/~bzg/org-contrib" :load "lisp")) ;; DON'T load system org (setq load-path (cl-remove-if (lambda (path) (string-match-p "lisp/org\\'" path)) load-path)) (setq org-adapt-indentation nil org-auto-align-tags t org-archive-mark-done t org-fold-catch-invisible-edits 'show-and-error org-clock-clocked-in-display 'mode-line org-clock-frame-title-format (cons '(t org-mode-line-string) (cons " --- " frame-title-format)) org-clock-string-limit 7 ; just the clock bit ;; org-clock-string-limit 25 ; gives enough information org-clock-persist nil org-confirm-babel-evaluate nil org-cycle-separator-lines 0 org-directory (sync/ "org/" t) org-ellipsis (or truncate-string-ellipsis "…") org-fontify-done-headline t org-fontify-quote-and-verse-blocks t org-fontify-whole-heading-line t org-hide-emphasis-markers t org-html-coding-system 'utf-8-unix org-image-actual-width (list (* (window-font-width) (- fill-column 8))) org-imenu-depth 3 org-indent-indentation-per-level 0 org-indent-mode-turns-on-hiding-stars nil org-insert-heading-respect-content t org-list-demote-modify-bullet '(("-" . "+") ("+" . "-")) org-log-done 'time org-log-into-drawer t org-num-skip-commented t org-num-skip-unnumbered t org-num-skip-footnotes t org-outline-path-complete-in-steps nil org-pretty-entities t org-pretty-entities-include-sub-superscripts nil org-refile-targets '((nil . (:maxlevel . 2)) (org-agenda-files . (:maxlevel . 1))) org-refile-use-outline-path 'file org-special-ctrl-a/e t org-special-ctrl-k t org-src-fontify-natively t org-src-tab-acts-natively t org-src-window-setup 'current-window org-startup-truncated nil org-startup-with-inline-images t org-tags-column -77 ;; (- (- fill-column 1 (length org-ellipsis))) org-todo-keywords '((sequence "TODO(t)" "WAIT(w@/!)" "ONGOING(o@)" "|" "DONE(d!)" "ASSIGNED(a@/!)") (sequence "|" "CANCELED(k@)") (sequence "MEETING(m)")) org-use-speed-commands t org-emphasis-alist '(("*" org-bold) ("/" org-italic) ("_" org-underline) ("=" org-verbatim) ("~" org-code) ("+" org-strikethrough))) (add-hook* 'org-mode-hook #'variable-pitch-mode #'visual-fill-column-mode #'turn-off-auto-fill #'org-indent-mode #'prettify-symbols-mode #'abbrev-mode) (define-local-before-save-hook org-mode (org-hide-drawer-all) (org-align-tags 'all)) (eval-after org (require '+org) (define-key* org-mode-map "C-M-k" #'kill-paragraph "C-M-t" #'transpose-paragraphs "RET" #'+org-return-dwim "S-" #'+org-table-copy-down|+org-return "C-c C-o" #'+org-open-at-point-dwim) (org-clock-persistence-insinuate)) (eval-after ol ; org-link (defmacro define-org-link-type (type args &rest body) "Define an org link TYPE with ARGS that does something. If BODY is blank, message the user about the link." (declare (indent 2) (doc-string 3) (debug (sexp sexp def-body))) (let ((fn (intern (format "org-%s-open" type))) (body (or body `((message ,(format "%s: %%S" type) ,(car args))))) (type-string (format "%s" type))) `(prog1 (defun ,fn ,args ,@body) (org-link-set-parameters ,type-string :follow #',fn)))) (define-org-link-type sms (number _)) (define-org-link-type tel (number _)))) (yoke org-agenda nil (setq org-agenda-skip-deadline-if-done t org-agenda-skip-scheduled-if-done t org-agenda-span 10 org-agenda-block-separator ?─ org-agenda-time-grid '((daily today require-timed) (800 1000 1200 1400 1600 1800 2000) " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄") org-agenda-current-time-string "← now ─────────────────────────────────────────────────" org-agenda-include-diary nil ; I use the org-diary features org-agenda-todo-ignore-deadlines 'near org-agenda-todo-ignore-scheduled 'future org-agenda-include-deadlines t org-deadline-warning-days 0 org-agenda-show-future-repeats 'next org-agenda-window-setup 'current-window org-agenda-file-skip-regexp "sync-conflict") (defcustom org-agenda-file-skip-regexp nil "Files matching this regexp are removed from `org-agenda-files'." :group 'org-agenda) (define-advice org-agenda-files (:filter-return (files) skip-regexp) (when org-agenda-file-skip-regexp (setq files (seq-remove (lambda (file) (string-match-p org-agenda-file-skip-regexp file)) files))) files) (setq-local-hook org-agenda-mode-hook truncate-lines t electric-pair-pairs (append electric-pair-pairs (mapcar (lambda (e) (let ((ch (string-to-char (car e)))) (cons ch ch))) org-emphasis-alist))) (add-hook* 'org-agenda-mode-hook #'hl-line-mode) (add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree) (define-key* (current-global-map) "C-c c" #'org-capture "C-c a" #'org-agenda) (eval-after org-capture '+org-capture)) (yoke ox ; org-export (eval-after org (require 'ox)) (eval-after ox (require* '+ox '(ox-md nil t)) (+org-export-pre-hooks-insinuate)) (setq org-export-coding-system 'utf-8-unix org-export-headline-levels 8 org-export-with-drawers nil org-export-with-section-numbers nil org-export-with-smart-quotes t org-export-with-sub-superscripts t org-export-with-toc nil)) (yoke (electric-cursor "https://codeberg.org/acdw/electric-cursor.el") (setq electric-cursor-alist '((overwrite-mode . hbar) (t . bar))) (electric-cursor-mode)) (yoke _work :depends ((bbdb "https://git.savannah.nongnu.org/git/bbdb.git" :load "lisp") (bbdb-vcard "https://github.com/tohojo/bbdb-vcard/")) (setf bbdb-complete-mail-allow-cycling t) (add-hook* '+custom-after-load-hook (defun _work@after-custom () (require* 'private '_work) (require* 'bbdb 'bbdb-message) (bbdb-initialize 'gnus 'message)))) (yoke (org-taskwise "https://codeberg.org/acdw/org-taskwise.el")) (yoke scule (require 'scule) (defvar scule-map (let ((map (make-sparse-keymap))) (define-key map (kbd "M-u") #'scule-upcase) (define-key map (kbd "M-l") #'scule-downcase) (define-key map (kbd "M-c") #'scule-capitalize) map) "Keymap for scule twiddling.") (define-key* (current-global-map) "M-c" scule-map "M-u" #'universal-argument) (define-key universal-argument-map (kbd "M-u") #'universal-argument-more)) (yoke (titlecase "https://codeberg.org/acdw/titlecase.el") (eval-after titlecase (add-to-list* 'titlecase-skip-words-regexps (rx word-boundary (+ (any upper digit)) word-boundary))) (eval-after scule (define-key* scule-map "M-t" #'titlecase-dwim))) (yoke (flyspell-correct "https://github.com/duckwork/flyspell-correct") (eval-after flyspell (require* 'flyspell-correct `(+flyspell-correct ,(locate-user-emacs-file "lisp/+flyspell-correct"))) (define-key* flyspell-mode-map "C-;" #'flyspell-correct-wrapper "" #'+flyspell-correct-buffer "C-," nil "C-." nil)) (add-hook 'org-mode-hook #'flyspell-mode) (setq flyspell-correct--cr-key ";")) (yoke (helpful "https://github.com/Wilfred/helpful") :depends ((dash "https://github.com/magnars/dash.el") (f "https://github.com/rejeep/f.el") (s "https://github.com/magnars/s.el") (elisp-refs "https://github.com/Wilfred/elisp-refs")) (define-key* (current-global-map) " f" #'helpful-callable " v" #'helpful-variable " k" #'helpful-key " ." #'helpful-at-point " o" #'helpful-symbol) (unless (featurep 'info-look) (run-with-idle-timer 1 nil (lambda () (require 'info-look) (let ((inhibit-message t)) (info-lookup-setup-mode 'symbol 'emacs-lisp-mode))))) (setf (alist-get "\\*helpful" display-buffer-alist nil nil #'string=) '((display-buffer-in-side-window) (side . bottom) (window-height . 20)))) (yoke (hippie-completing-read "https://codeberg.org/acdw/hippie-completing-read.el") (define-key* (current-global-map) "M-/" #'hippie-completing-read)) (yoke dictionary ; Comes with Emacs 29! (setq dictionary-server (if (or (executable-find "dictd") (file-exists-p "/usr/sbin/dictd")) ; oh debian "localhost" "dict.org")) (setf (alist-get "^\\*Dictionary\\*" display-buffer-alist nil nil #'string=) '((display-buffer-in-side-window) (side . bottom) (window-height . 20))) (eval-after org (define-key* org-mode-map "M-@" #'dictionary-search)) (eval-after embark (define-key* embark-identifier-map "@" #'dictionary-search))) (yoke (anzu "https://github.com/emacsorphanage/anzu") (require 'anzu) (global-anzu-mode) (define-key* (current-global-map) [remap query-replace] #'anzu-query-replace-regexp [remap query-replace-regexp] #'anzu-query-replace) (define-key* isearch-mode-map [remap isearch-query-replace] #'anzu-isearch-query-replace-regexp [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace) (defun anzu-qr@window (fn &rest r) "ADVICE to query-replace from the beginning of the window." (let ((scroll-margin 0)) (cond ((region-active-p) (apply fn r)) (:else (save-excursion (goto-char (window-start)) (apply fn r)))))) (advice-add 'anzu-query-replace-regexp :around #'anzu-qr@window) (advice-add 'anzu-query-replace :around #'anzu-qr@window)) (yoke tempo (require '+tempo)) (yoke (0x0 "https://gitlab.com/willvaughn/emacs-0x0") (setf 0x0-default-server 'ttm) (define-advice 0x0-shorten-uri (:around (fn server uri) use-0x0) (interactive (list (cdr (assq 'envs 0x0-servers)) (read-string "URI: "))) (funcall fn server uri)) (eval-after embark (define-key* embark-region-map "U" #'0x0-dwim))) (yoke (filldent "https://codeberg.org/acdw/filldent.el") (define-key* (current-global-map) "M-q" #'filldent-unfill-toggle)) (yoke (avy "https://github.com/abo-abo/avy") (require 'avy) (setf avy-background t (alist-get ?. avy-dispatch-alist) (defun avy-action-embark (pt) (unwind-protect (save-excursion (goto-char pt) (embark-act)) (select-window (cdr (ring-ref avy-ring 0)))) t)) (define-key* (current-global-map) "M-j" #'avy-goto-char-timer) (define-key* isearch-mode-map "M-j" #'avy-isearch)) (yoke (frowny "https://codeberg.org/acdw/frowny.el") (setf frowny-eyes (rx (any ":=") (opt "'") (? "-"))) (global-frowny-mode)) (yoke (isearch-mb "https://github.com/astoff/isearch-mb") (eval-after (consult anzu) (require 'isearch-mb) (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)) (yoke (keepassxc-shim "https://codeberg.org/acdw/keepassxc-shim.el") (keepassxc-shim-activate)) (yoke (keychain-environment "https://github.com/tarsius/keychain-environment") :when (executable-find "keychain") (keychain-refresh-environment)) (yoke (exec-path-from-shell "https://github.com/purcell/exec-path-from-shell") :when (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)) (yoke (sophomore "https://codeberg.org/acdw/sophomore.el") (sophomore-enable-all) (sophomore-disable #'view-hello-file #'describe-gnu-project) (sophomore-disable-with 'confirm #'save-buffers-kill-terminal)) (yoke (macrostep "https://github.com/joddie/macrostep") (eval-after elisp-mode (require 'macrostep)) (define-key* '(emacs-lisp-mode-map lisp-interaction-mode-map) "C-c e" #'macrostep-expand)) (yoke (embrace "https://github.com/cute-jumper/embrace.el") :depends ((expand-region "https://github.com/magnars/expand-region.el")) (define-key* (current-global-map) "C-=" #'er/expand-region "C-," #'embrace-commander) (eval-after org (define-key* org-mode-map "C-=" #'er/expand-region "C-," #'embrace-commander)) (dolist (fnhook '((org-mode-hook embrace-org-mode-hook) (ruby-mode-hook embrace-ruby-mode-hook) (emacs-lisp-mode-hook embrace-emacs-lisp-mode-hook) (latex-mode-hook embrace-LaTeX-mode-hook))) (apply #'add-hook fnhook)) (eval-after org (defmacro org-insert-or-embrace (char) "Define a function to insert CHAR, or `embrace' the region with it." (let* ((fn-name (intern (format "org-insert-or-embrace-%s" char))) (char (cond ((characterp char) char) ((stringp char) (string-to-char char)) (t (user-error "Bad format for char: %S" char))))) `(defun ,fn-name (n) ,(format "Insert N %ss, or surround the region with them." (char-to-string char)) (interactive "p") (if (region-active-p) (dotimes (_ n) (embrace--add-internal (region-beginning) (region-end) ,char) (forward-char 1)) (self-insert-command n ,char))))) (define-key* org-mode-map "*" (org-insert-or-embrace "*") "/" (org-insert-or-embrace "/") "_" (org-insert-or-embrace "_") "=" (org-insert-or-embrace "=") "~" (org-insert-or-embrace "~") "+" (org-insert-or-embrace "+")))) (yoke (notmuch "~/usr/share/emacs/site-lisp") (eval-after bbdb (require* 'notmuch '+notmuch '+message)) (+define-dir notmuch/ (sync/ "emacs/notmuch") "Notmuch configuration and data.") (setf notmuch-init-file (notmuch/ "notmuch-init.el" t) notmuch-address-save-filename (notmuch/ "addresses" t) notmuch-address-use-company (featurep 'company) notmuch-search-oldest-first nil notmuch-archive-tags '("-inbox" "-unread") notmuch-draft-tags '("+draft" "-inbox" "-unread")) (define-key* (current-global-map) "C-c m" #'notmuch-mua-new-mail "C-c n" #'+notmuch-goto) ;; Reading mail (setf notmuch-show-indent-content nil) (add-hook* '(notmuch-show-mode-hook notmuch-message-mode-hook) #'visual-fill-column-mode) (eval-after notmuch (define-key* notmuch-search-mode-map "RET" #'notmuch-search-show-thread "M-RET" #'notmuch-tree-from-search-thread "!" #'+notmuch-search-mark-spam) (define-key* notmuch-tree-mode-map "!" #'+notmuch-search-mark-spam-then-next)) ;; Writing mail (setf message-kill-buffer-on-exit t message-auto-save-directory nil) ;; Sending mail (setf send-mail-function #'sendmail-send-it mail-specify-envelope-from t message-sendmail-envelope-from 'header message-envelope-from 'header) ;; Extras (define-advice mm-save-part-to-file (:before (_handle file) create-directory) (let ((directory (file-name-directory file))) (when (yes-or-no-p (format "Directory %s doesn't exist. Create?" directory)) (make-directory directory :parents)))) (eval-after notmuch (require '+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) (advice-add 'notmuch-bury-or-kill-this-buffer :after (defun +display-time@notmuch (&rest _) ;; (display-time-event-handler) (display-time-update))) (setf notmuch-saved-searches (list (list :name "inbox+unread" :query (+notmuch-query-concat "tag:inbox" "tag:unread" "NOT tag:Spam") :key "m" :search-type 'tree) (list :name "inbox" :query (+notmuch-query-concat "tag:inbox" "NOT tag:Spam") :key "i" :search-type 'tree) (list :name "lists+unread" :query (+notmuch-query-concat "tag:/List/" "tag:unread") :key "l" :search-type 'tree) (list :name "lists" :query "tag:/List/" :key "L" :search-type 'tree) (list :name "unread" :query (+notmuch-query-concat "tag:unread" "NOT tag:Spam") :key "u" :search-type 'tree) (list :name "flagged" :query "tag:flagged" :key "f" :search-type 'tree) (list :name "sent" :query "tag:sent" :key "t" :search-type 'tree) (list :name "drafts" :query "tag:draft" :key "d" :search-type 'tree) (list :name "all mail" :query "*" :key "a" :search-type 'tree))))) (yoke (cider "https://github.com/clojure-emacs/cider") :depends ((clojure-mode "http://github.com/clojure-emacs/clojure-mode") (parseedn "https://github.com/clojure-emacs/parseedn/") (parseclj "https://github.com/clojure-emacs/parseclj/") ; parseedn (queue "https://elpa.gnu.org/packages/queue-0.2.el" :type 'http) (spinner "https://github.com/Malabarba/spinner.el") (sesman "https://github.com/vspinu/sesman")) :when (executable-find "clojure")) (yoke (web-mode "https://github.com/fxbois/web-mode") (setf (alist-get (rx "." (or "htm" "html" "phtml" "tpl.php" "asp" "gsp" "jsp" "ascx" "aspx" "erb" "mustache" "djhtml") eos) auto-mode-alist nil nil #'string=) 'web-mode)) (yoke (chicken-geiser "https://gitlab.com/emacs-geiser/chicken") :depends ((geiser "https://gitlab.com/emacs-geiser/geiser" :load "elisp")) :when (executable-find "csi") :pre ((autoload 'geiser-activate-implementation "geiser-impl")) (autoload 'geiser "geiser" nil :interactive) (add-hook 'scheme-mode-hook 'geiser-mode)) (yoke (zoom-frm "https://github.com/emacsmirror/zoom-frm") :depends ((frame-cmds "https://github.com/emacsmirror/frame-cmds") (frame-fns "https://github.com/emacsmirror/frame-fns")) (define-key* (current-global-map) "M-+" #'zoom-frm-in "M-_" #'zoom-frm-out)) (yoke (jabber "https://codeberg.org/acdw/emacs-jabber") :depends ((srv "https://github.com/legoscia/srv.el") (fsm "https://elpa.gnu.org/packages/fsm-0.2.1.el" :type 'http)) (setf jabber-account-list '(("acdw@hmm.st")) jabber-auto-reconnect t jabber-chat-buffer-format "xmpp:%n" jabber-browse-buffer-format "xmpp-browse:%n" jabber-groupchat-buffer-format "xmpp-muc:%n" jabber-muc-private-buffer-format "xmpp-muc-private:%n" jabber-groupchat-prompt-format "%>10n │ " jabber-chat-local-prompt-format "%>10n │ " jabber-chat-system-prompt-format " * * * * * *" jabber-chat-foreign-prompt-format "%>10n │ " jabber-muc-private-foreign-prompt-format "%g/%n " jabber-last-read-marker "----------------------------------------" jabber-muc-header-line-format '("" jabber-muc-topic) jabber-muc-decorate-presence-patterns '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$") ("." . jabber-muc-presence-dim)) jabber-activity-make-strings #'jabber-activity-make-strings-shorten ;; (defun +jabber-activity-make-strings (jids) ;; (mapcar (lambda (jid) ;; (cons jid ;; (let ((s (jabber-activity-make-string-default jid))) ;; (cond ;; ((string-match-p "%" s) ;; (replace-regexp-in-string "%.*" "" s)) ;; (:else s))))) ;; jids)) jabber-rare-time-format " - - - - - - %H:00 %F") (defun +electric-pair-disable-local-mode () (electric-pair-local-mode -1)) (add-hook* '(jabber-chat-mode-hook jabber-browse-mode-hook jabber-roster-mode-hook jabber-console-mode-hook) #'visual-fill-column-mode #'+electric-pair-disable-local-mode) (defun +jabber-fix-keybinds-dammit () "Jabber autoloads keybinds which is really annoying." (define-key* (current-global-map) "C-x C-j" #'dired-jump "C-c j" jabber-global-keymap "C-c C-SPC" #'jabber-activity-switch-to)) (eval-after init (+jabber-fix-keybinds-dammit)) (eval-after jabber (require 'jabber-httpupload nil :noerror) (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons) (remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo) (remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo) (add-hook 'jabber-alert-muc-hooks (defun jabber@highlight-acdw (&optional _nick _group buf _text _title) (when buf (with-current-buffer buf (let ((regexp (rx word-boundary "acdw" ; maybe get from the config? word-boundary))) (hi-lock-unface-buffer regexp) (highlight-regexp regexp 'hi-blue)))))) (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus) (+jabber-fix-keybinds-dammit)) ;; (add-hook* 'jabber-activity-mode-hook ;; (defun +jabber-activity-mode@move-to-end-of-mode-line () ;; (setf global-mode-string ;; (append (delete '(t jabber-activity-mode-string) ;; global-mode-string) ;; '((t jabber-activity-mode-string)))))) (setq-local-hook jabber-chat-mode-hook wrap-prefix (format "%10s " " ") mode-line-buffer-identification (pcase (buffer-name) ((rx "%") ; biboumi irc channel ;; xmpp-muc:#scheme%irc.libera.chat@irc.hmm.st (propertized-buffer-identification (replace-regexp-in-string "xmpp-muc:\\([^%]*\\)%\\([^@]*\\)@.*" "\\1@\\2" (buffer-name)))) (_ ; xmpp channel (propertized-buffer-identification "%12b")))) (defun jabber-chat@after-modus-themes-load () (modus-themes-with-colors (custom-set-faces `(jabber-chat-prompt-foreign ((t :foreground unspecified :inherit modus-themes-bold)) :now) `(jabber-chat-prompt-local ((t :foreground unspecified :inherit modus-themes-bold)) :now) `(jabber-chat-prompt-system ((t :foreground unspecified :inherit modus-themes-bold)) :now) `(jabber-activity-face ((t :slant italic))) `(jabber-activity-personal-face ((t :slant italic :weight bold))) `(jabber-rare-time-face ((t :inherit font-lock-comment-face))))) (setq jabber-muc-nick-value (pcase (frame--current-backround-mode (selected-frame)) ('light 0.5) ('dark 1.0)))) (eval-after modus-themes (add-hook 'modus-themes-after-load-theme-hook #'jabber-chat@after-modus-themes-load)) (when (or (custom-theme-enabled-p 'modus-operandi) (custom-theme-enabled-p 'modus-vivendi)) (jabber-chat@after-modus-themes-load)) (eval-after (consult jabber) ;; Jabber.el chat buffers source for `consult-buffer' (defvar jabber-chat-buffer-source `( :name "Jabber" :hidden nil :narrow ?j :category buffer :state ,#'consult--buffer-state :items ,(lambda () (mapcar #'buffer-name (seq-filter (lambda (buf) (with-current-buffer buf (eq major-mode 'jabber-chat-mode))) (buffer-list)))))) (add-to-list 'consult-buffer-sources 'jabber-chat-buffer-source :append) ;; Also hide xmpp buffers from regular buffer list (add-to-list 'consult-buffer-filter "\\`xmpp" nil #'string-equal))) (yoke (link-hint "https://github.com/noctuid/link-hint.el/") :depends ((avy "https://github.com/abo-abo/avy")) (require '+link-hint) (+link-hint-open-secondary-setup) (+link-hint-open-chrome-setup) (setf link-hint-avy-style 'at-full link-hint-avy-all-windows t) (global-set-key (kbd "M-l") +link-hint-map) (define-key* +link-hint-map "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)) (yoke (elpher "git://thelambdalab.xyz/elpher.git") (eval-after elpher (define-key* elpher-mode-map "l" #'elpher-back))) (yoke (epithet "https://github.com/oantolin/epithet") (add-hook* '(Info-selection-hook help-mode-hook occur-mode-hook shell-mode-hook) #'epithet-rename-buffer) (cond ((boundp 'eww-auto-rename-buffer) (setf eww-auto-rename-buffer 'title)) (:else (add-hook 'eww-after-render-hook #'epithet-rename-buffer)))) (yoke browse-url (require '+browse-url) (setf browse-url-browser-function #'eww-browse-url browse-url-chrome-program (seq-some #'executable-find '("chromium" "chrome" "google-chrome-stable")) browse-url-firefox-program (seq-some #'executable-find '("firefox" "firefox-esr")) browse-url-generic-program (or browse-url-firefox-program browse-url-chrome-program) browse-url-firefox-new-window-is-tab t browse-url-firefox-arguments "-new-tab" browse-url-handlers `((video-url-p . +browse-url-with-mpv) (music-url-p . +browse-url-with-mpv) (image-url-p . +browse-image-with-mpv) (blobp . +browse-url-download) (external-url-p . ,browse-url-secondary-browser-function))) (+browse-url-make-external-viewer-handler "mpv" '("--cache-pause-wait=30" "--cache-pause-initial=yes") "Video URL: " :fallback browse-url-secondary-browser-function) (+browse-url-make-external-viewer-handler "mpv" '("--image-display-duration=inf") "Image URL: " :name +browse-image-with-mpv) (defun video-url-p (url) "Is URL a video?" (string-match-p (rx (or "youtube.com" "youtu.be" "invidious" "yewtu.be" (seq "." (or "mp4" "gif" "mov" "MOV" "webm") eos))) url)) (defun music-url-p (url) "Is URL music?" (string-match-p (rx "soundcloud.com" "bandcamp.com" (seq "." (or "ogg" "mp3" "opus" "m4a") eos)) url)) (defun image-url-p (url) "Is URL an image?" (string-match-p (rx "." (or "jpeg" "jpg" "png" "bmp" "webp") eos) url)) (defun external-url-p (url) "Should URL open in an external browser?" (string-match-p (rx (or "github.com" "gitlab.com" "codeberg.org" "tildegit.org" "git.tilde.town" "google.com" "imgur.com" "twitch.tv" "pixelfed" "instagram.com" "bibliogram.art" "reddit.com" "teddit.net" "twitter.com" "nitter" "t.co" "streamable.com" "spotify.com" "hetzner.cloud" "melpa.org")) url)) (defun blobp (url) "Is URL some other blob that can't open in Emacs?" (string-match-p (rx (or (: (or ".tar.gz" ".pdf") eos))) url)) (eval-after chd (add-to-list 'browse-url-handlers (cons chd/url-regexps #'browse-url-chrome))) (require 'browse-url-transform) (setf browse-url-transform-alist `(("twitter\\.com" . "nitter.net") ("\\(?:\\(?:old\\.\\)?reddit\\.com\\)" . "libreddit.de") ("medium\\.com" . "scribe.rip") ("www\\.npr\\.org" . "text.npr.org"))) (browse-url-transform-mode)) (yoke eww (defun +eww-browse-with-external-browser (&optional url) "Browse URL with an external browser and close eww." (interactive nil eww-mode) (condition-case e ;; This is wrapped in a `condition-case' so that the eww window won't ;; close if there's an error calling the browser. (funcall browse-url-secondary-browser-function (or url (plist-get eww-data :url))) (:success (when (null url) ; interactive (quit-window))) (t (signal (car e) (cdr e))))) (define-key* eww-mode-map "&" #'+eww-browse-with-external-browser)) (yoke tab-bar (setf tab-bar-show t global-mode-string '((jabber-activity-mode jabber-activity-mode-string) " ⋅" display-time-string "|")) (add-to-list 'tab-bar-format 'tab-bar-format-align-right :append) (add-to-list 'tab-bar-format 'tab-bar-format-global :append) (tab-bar-mode)) (yoke (pdf-tools "https://github.com/vedang/pdf-tools" :load "lisp") :depends ((tablist "https://github.com/politza/tablist/")) :when (executable-find "epdfinfo") ; installed from Debian repos (pdf-tools-install))