;;; init.el --- An Emacs of one's own -*- lexical-binding: t -*- ;; Author: Case Duckworth , with inspo from many others ;; Homepage: https://git.acdw.net/emacs ;; Config-Requires: ((emacs "29.0")) ;; Bankruptcy: 9.4 ;; This configuration is Free Software. Everyone is permitted to do whatever ;; they want with it, without limitation. This software comes without any ;; warranty whatsoever, but with two pieces of advice: ;; ;; - Don't hurt others. ;; - Make good choices. ;;; Code: (load (locate-user-emacs-file "basics")) ; super basic stuff ;;; Built-ins (use-package emacs ; Misc. config :config (setopt recenter-positions '(top middle bottom) initial-major-mode 'lisp-interaction-mode initial-scratch-message ";; Emacs!\n\n" ;; (format "%s\n\n" ;; (mapconcat (lambda (s) (format ";; %s" s)) ;; (process-lines "fortune" "-s") ;; "\n")) eval-expression-print-level nil eval-expression-print-length nil x-select-enable-clipboard-manager nil) ;; TODO: move this ... elsewhere (setopt mode-line-format '("%e" mode-line-front-space ;; (:propertize ("" mode-line-mule-info ;; mode-line-client ;; mode-line-modified ;; mode-line-remote) ;; display (min-width (5.0))) ("" mode-line-mule-info mode-line-client mode-line-modified mode-line-remote) mode-line-frame-identification mode-line-buffer-identification " " mode-line-position (vc-mode vc-mode) " " minions-mode-line-modes mode-line-misc-info mode-line-end-spaces)) (keymap-global-unset "C-\\") (keymap-global-unset "") (setf (alist-get "\\*Compile-Log\\*" display-buffer-alist nil nil #'equal) '(display-buffer-no-window)) (add-hook 'after-init-hook (defun global-mode-string@setup () (defvar jabber-activity-mode-string) (defvar org-mode-line-string) (defvar display-time-mode) (defvar display-time-string) (setf global-mode-string '((t jabber-activity-mode-string) org-mode-line-string (display-time-mode display-time-string))))) (keymap-global-set "C-c t" (define-keymap :prefix 'toggle-map "e" #'toggle-debug-on-error "q" #'toggle-debug-on-quit "c" #'column-number-mode "l" #'line-number-mode "L" #'display-line-numbers-mode))) (use-package faces :config (add-hook 'server-after-make-frame-hook (defun first-frame@set-fonts () (remove-hook 'server-after-make-frame-hook #'first-frame@set-fonts) (face-spec-set 'default `((t :family ,(find-font "Recursive Mono Casual Static" "Comic Code" "DejaVu Sans Mono") :height 100))) (face-spec-set 'fixed-pitch `((t :family ,(find-font "Recursive Mono Casual Static" "Comic Code" "DejaVu Sans Mono") :height 1.0))) (face-spec-set 'variable-pitch `((t :family ,(find-font "Recursive Sans Linear Static" "Atkinson Hyperlegible" "DejaVu Serif") :height 1.0))) (face-spec-set 'font-lock-comment-face `((t :inherit variable-pitch))) (face-spec-set 'font-lock-string-face `((t :inherit variable-pitch))) ;; Emojis (cl-loop with ffl = (font-family-list) for font in '("Noto Emoji" "Noto Color Emoji" "Segoe UI Emoji" "Apple Color Emoji" "FreeSans" "FreeMono" "FreeSerif" "Unifont" "Symbola") if (member font ffl) do (set-fontset-font t 'symbol font)) ;; International scripts (cl-loop with ffl = (font-family-list) for (charset . font) in '((latin . "Noto Sans") (han . "Noto Sans CJK SC Regular") (kana . "Noto Sans CJK JP Regular") (hangul . "Noto Sans CJK KR Regular") (cjk-misc . "Noto Sans CJK KR Regular") (khmer . "Noto Sans Khmer") (lao . "Noto Sans Lao") (burmese . "Noto Sans Myanmar") (thai . "Noto Sans Thai") (ethiopic . "Noto Sans Ethiopic") (hebrew . "Noto Sans Hebrew") (arabic . "Noto Sans Arabic") (gujarati . "Noto Sans Gujarati") (devanagari . "Noto Sans Devanagari") (kannada . "Noto Sans Kannada") (malayalam . "Noto Sans Malayalam") (oriya . "Noto Sans Oriya") (sinhala . "Noto Sans Sinhala") (tamil . "Noto Sans Tamil") (telugu . "Noto Sans Telugu") (tibetan . "Noto Sans Tibetan")) if (member font ffl) do (set-fontset-font t charset font)))) (unless (daemonp) (run-with-idle-timer 1 nil #'first-frame@set-fonts))) (use-package text-mode :config (add-hook 'text-mode-hook #'abbrev-mode)) (use-package prog-mode :config (setopt tab-width 8 sh-indentation tab-width) (add-hook 'prog-mode-hook #'auto-fill-mode) (add-hook 'prog-mode-hook (defun prog@indent-tabs-maybe () (indent-tabs-mode (if (derived-mode-p 'emacs-lisp-mode 'lisp-mode 'scheme-mode 'python-mode 'haskell-mode) -1 1))))) (use-package auth-source :config (setopt auth-sources '(default "secrets:passwords")) (add-hook 'auth-info-hook #'truncate-lines-local-mode)) (use-package fringe :config (fringe-mode '(nil . 0))) (use-package ispell :config (setopt ispell-program-name (choose-executable "aspell" "ispell")) ;; (add-hook 'before-save-hook ;; #'+ispell-move-buffer-words-to-dir-locals-hook) (put 'ispell-buffer-session-localwords 'safe-local-variable '+ispell-safe-local-p)) (use-package dired :bind (("C-x C-j" . dired-jump) ([remap list-directory] . dired) :map dired-mode-map ("C-j" . dired-up-directory) ("" . dired-up-directory)) :config (require 'dired-x) (setopt 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 "-AlFhv --group-directories-first" 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) (add-hook 'dired-mode-hook #'dired-hide-details-mode) (add-hook 'dired-mode-hook #'hl-line-mode) (add-hook 'dired-mode-hook #'truncate-lines-local-mode)) (use-package dictionary :custom (dictionary-server (if (or (executable-find "dictd") (file-exists-p "/usr/sbin/dictd")) ; debian "localhost" "dict.org")) :bind (("C-c w d" . dictionary-search)) :config (setf (alist-get "\\*Dictionary\\*" display-buffer-alist nil nil #'equal) '(display-buffer-in-side-window (window-width . 80) (side . right)))) (use-package calendar :custom (diary-file (private/ "diary"))) (use-package mouse :config (setopt context-menu-functions '(context-menu-undo context-menu-region context-menu-middle-separator context-menu-local context-menu-minor)) (context-menu-mode)) (use-package password-cache :config (setopt password-cache t password-cache-expiry 3600)) (use-package time :config (setopt display-time-format " %H:%M" display-time-interval 60 display-time-use-mail-icon t 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-default-load-average nil) (with-eval-after-load 'notmuch (add-hook 'notmuch-after-tag-hook #'display-time-update)) (display-time-mode)) (use-package tab-bar :config (setopt tab-bar-show t tab-bar-close-button-show t) ;; (add-to-list 'tab-bar-format 'tab-bar-format-menu-bar) (add-to-list 'tab-bar-format 'tab-bar-format-align-right :append) (add-to-list 'tab-bar-format 'tab-bar-format-global :append) (if (daemonp) (add-hook 'server-after-make-frame-hook (defun after-frame@tab-bar () (tab-bar-mode) (remove-hook 'server-after-make-frame-hook #'after-frame@tab-bar))) (run-with-idle-timer 2 nil #'tab-bar-mode))) (use-package info :preface (defun Info-copy-current-node-name-0 () "Call `Info-copy-current-node-name' with a 0 prefix arg." (interactive) (Info-copy-current-node-name 0)) :bind (:map Info-mode-map ("w" . Info-copy-current-node-name-0) ("c" . Info-copy-current-node-name))) (use-package make-mode :defer t :config (add-hook 'makefile-mode-hook (defun make-mode@setup () (remove-hook 'write-file-functions #'makefile-warn-suspicious-lines t) (remove-hook 'write-file-functions #'makefile-warn-continuations t)))) (use-package eglot :preface (defun +eglot-eldoc () ;; https://www.masteringemacs.org/article/seamlessly-merge-multiple-documentation-sources-eldoc (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)) :hook ((bash-ts-mode . eglot-ensure) (scheme-mode . eglot-ensure)) :config (add-to-list 'eglot-server-programs '(scheme-mode . ("chicken-lsp-server"))) (add-hook 'eglot-managed-mode #'+eglot-eldoc)) (use-package eldoc :config (setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) (setf (alist-get "^\\*eldoc for" display-buffer-alist nil nil #'equal) '(display-buffer-at-bottom (window-height . 4))) (eldoc-add-command-completions "paredit-")) (use-package pulse :config (setopt pulse-flag nil pulse-delay 1 pulse-iterations 1)) (use-package flyspell :hook (org-mode-hook)) (use-package display-fill-column-indicator :hook (prog-mode-hook)) (use-package package :config (defun package-update-async-in-progress (&rest _) (message "Package async update in progress.")) (defun package-update-all-async () "Update packages asyncronously." (interactive) (let ((message "Package update (async)...") (disable-fns '(package-update package-update-all package-update-all-async))) (dolist (fn disable-fns) (advice-add fn :override #'package-update-async-in-progress)) (message "%s" message) (unwind-protect (async-start `(lambda () (package-initialize) (package-update-all)) `(lambda (result) (message "%s %s" ,message result))) (dolist (fn ',disable-fns) (advice-remove fn 'package-update-async-in-progress)))))) (use-package ielm ;; https://www.n16f.net/blog/making-ielm-more-comfortable/ :preface (defun +ielm-init-history () (let ((path (etc/ "ielm/history" t))) (setq-local comint-input-ring-file-name path)) (setq-local comint-input-ring-size 10000) (setq-local comint-input-ignoredups t) (ignore-errors (comint-read-input-ring))) (defun +ielm-write-history (&rest _args) (with-file-modes #o600 (comint-write-input-ring))) (defun +ielm (&optional buf-name) "Interactively evaluate Emacs Lisp expressions. Switches to the buffer named BUF-NAME if provided (`*ielm*' by default), or creates it if it does not exist. See `inferior-emacs-lisp-mode' for details." (interactive) (let (old-point (buf-name (or buf-name "*ielm*"))) (unless (comint-check-proc buf-name) (with-current-buffer (get-buffer-create buf-name) (unless (zerop (buffer-size)) (setq old-point (point))) (inferior-emacs-lisp-mode))) (pop-to-buffer buf-name) (when old-point (push-mark old-point)))) :bind (:map emacs-lisp-mode-map ("C-c C-z" . +ielm)) :config (add-hook 'ielm-mode-hook #'eldoc-mode) (add-hook 'ielm-mode-hook #'+ielm-init-history) (advice-add 'ielm-send-input :after #'+ielm-write-history)) ;;; Applications (use-package acdw-mail :load-path "lisp/" :demand t :bind (("C-c n" . +notmuch-goto))) (use-package acdw-org :load-path "lisp/" :config (global-set-key [f8] #'org-clock-out)) (use-package acdw-shell :load-path "lisp/") (use-package acdw-web :load-path "lisp/") (use-package acdw-chat :load-path "lisp/") (use-package _work :load-path "~/sync/emacs/private/") ;;; Locally-developed packages (use-package +scratch :load-path "lisp/" :config (setopt +scratch-save-dir (sync/ "emacs/scratch.d/" t)) (add-hook 'kill-buffer-query-functions #'+scratch@immortal) (add-hook 'kill-emacs-hook #'+scratch-save-on-exit) (with-current-buffer (get-scratch-buffer-create) (local-set-key (kbd "C-x C-s") #'+scratch-save)) ;; Save *scratch* every hour (run-at-time t (* 60 60) #'+scratch-save "%FT%H%z") ;; Clean old *scratch* saves every day (run-at-time t (* 60 60 24) #'+scratch-clean)) (use-package pulse-location :load-path "~/src/emacs/pulse-location/" :config (pulse-location-mode)) (use-package dawn :load-path "~/src/emacs/dawn/" :after custom-allowed :config (add-hook 'custom-allowed-after-load-hook (defun dawn-modus () (dawn-schedule-themes 'modus-operandi 'modus-vivendi))) (setopt modus-themes-mixed-fonts t) (load-theme 'modus-vivendi t) (add-hook 'dawn-after-load-theme-hook (defun +reset-faces () (dolist (face '(font-lock-regexp-face font-lock-variable-name-face font-lock-preprocessor-face font-lock-remove-face font-lock-delimiter-face font-lock-label-face font-lock-operator-face font-lock-property-face font-lock-builtin-face font-lock-number-face font-lock-set-face font-lock-warning-face font-lock-punctuation-face font-lock-constant-face font-lock-type-face font-lock-function-name-face font-lock-reference-face font-lock-negation-char-face font-lock-misc-punctuation-face font-lock-escape-face font-lock-bracket-face)) (face-spec-set face '((t :foreground unspecified :background unspecified)))) (face-spec-set 'font-lock-keyword-face '((t :foreground unspecified :background unspecified :weight bold))) (face-spec-set 'font-lock-doc-face '((t :slant italic))))) (+reset-faces)) (use-package electric-cursor :load-path "~/src/emacs/electric-cursor/" :config (setopt electric-cursor-alist '((overwrite-mode . box) (t . bar))) (electric-cursor-mode)) (use-package mode-line-bell :load-path "~/src/emacs/mode-line-bell/" :config (setopt mode-line-bell-flash-time 0.25) (mode-line-bell-mode)) (use-package titlecase :load-path "~/src/emacs/titlecase.el/" :preface (defun +titlecase-sentence-style-dwim (&optional arg) "Titlecase a sentence. With prefix ARG, toggle the value of `titlecase-downcase-sentences' before sentence-casing." (interactive "P") (let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences) titlecase-downcase-sentences))) (titlecase-dwim 'sentence))) (defun +titlecase-org-headings () (interactive) (require 'org) (save-excursion (goto-char (point-min)) ;; See also `org-map-tree'. I'm not using that function because I want to ;; skip the first headline. A better solution would be to patch ;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc). (let ((level (funcall outline-level)) (org-special-ctrl-a/e t)) (while (and (progn (outline-next-heading) (> (funcall outline-level) level)) (not (eobp))) (titlecase-region (progn (org-beginning-of-line) (point)) (progn (org-end-of-line) (point))))))) :config (with-eval-after-load 'scule (keymap-set scule-map "M-t" #'titlecase-dwim))) (use-package scule :load-path "~/src/emacs/scule/" :bind-keymap ("M-c" . scule-map) :init ;; Use M-u for prefix keys (keymap-global-set "M-u" #'universal-argument) (keymap-set universal-argument-map "M-u" #'universal-argument-more)) (use-package filldent :load-path "~/src/emacs/filldent/" :bind ("M-q" . filldent-dwim) :config (setopt filldent-fill-modes '(web-mode))) (use-package frowny :load-path "~/src/emacs/frowny/" :config (global-frowny-mode)) (use-package keepassxc-shim :load-path "~/src/emacs/keepassxc-shim/" :config (keepassxc-shim-activate)) (use-package hippie-completing-read :load-path "~/src/emacs/hippie-completing-read/" :bind (("M-/" . hippie-completing-read))) ;;; External packages (use-package async :ensure t :config ;; https://github.com/jwiegley/emacs-async/issues/64 ;; (setopt message-send-mail-function #'async-smtpmail-send-it) (dired-async-mode) (async-bytecomp-package-mode)) (use-package trashed :ensure t) (use-package form-feed :ensure t :hook (prog-mode-hook)) (use-package clean-kill-ring :vc (:url "https://github.com/NicholasBHubbard/clean-kill-ring.el") :config (setopt clean-kill-ring-prevent-duplicates t) (clean-kill-ring-mode)) (use-package minions :ensure t :config (minions-mode)) (use-package visual-fill-column :preface (defcustom visual-fill-column-widen-amount 4 "Amount to widen `fill-column' by in `visual-fill-column-mode'." :type 'natnum :group 'visual-fill-column) (defun visual-fill-column--widen/narrow-handle-arg (cols) (cond ((null cols) visual-fill-column-widen-amount) ((listp cols) (* visual-fill-column-widen-amount (1+ (/ (car cols) 4)))) ((eq '- cols) (- visual-fill-column-widen-amount)) (:else cols))) (defun visual-fill-column-widen (&optional cols) "Widen `fill-column' by COLS, and re-display. If COLS is missing or nil, widen by `visual-fill-column-widen-amount'. When called with a plain \\[universal-argument], multiply that amount by 1 + the amount of \\[universal-argument]s. If called with a numerical prefix argument, widen by that number of columns." (interactive "P") (let ((cols (visual-fill-column--widen/narrow-handle-arg cols))) (cl-incf fill-column cols) (visual-fill-column-adjust) (message "Fill-column: %s" fill-column))) (defun visual-fill-column-narrow (&optional cols) "Narrow `fill-column' by COLS, then redisplay. The prefix argument is as in `visual-fill-column-widen' but negated." (interactive "P") (let ((cols (visual-fill-column--widen/narrow-handle-arg cols))) (cl-decf fill-column cols) (visual-fill-column-adjust) (message "Fill-column: %s" fill-column))) :ensure t :config (setopt visual-fill-column-center-text t visual-fill-column-extra-text-width '(3 . 3) visual-fill-column-width (+ fill-column 4)) (keymap-set toggle-map "v" #'visual-fill-column-mode) (keymap-set visual-fill-column-mode-map "C-x C->" #'visual-fill-column-widen) (keymap-set visual-fill-column-mode-map "C-x C-<" #'visual-fill-column-narrow) (add-hook 'visual-fill-column-mode-hook #'visual-line-mode) (add-hook 'eww-mode-hook #'visual-fill-column-mode) (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) (use-package mlscroll :ensure t :defer 1 :after modus-themes :preface (define-advice load-theme (:after (&rest _) mlscroll) (mlscroll-mode -1) (when (seq-intersection '(modus-vivendi modus-operandi) custom-enabled-themes) (modus-themes-with-colors (setq mlscroll-in-color fg-dim mlscroll-out-color bg-inactive))) (run-with-idle-timer 1 nil #'mlscroll-mode)) :config (load-theme@mlscroll)) (use-package cape :ensure t :config (add-hook 'completion-at-point-functions #'cape-file 90) (add-hook 'completion-at-point-functions #'cape-dabbrev 91) (advice-add 'emacs-completion-at-point :around #'cape-wrap-nonexclusive)) (use-package wgrep :ensure t :config (setopt wgrep-enable-key (kbd "C-x C-q")) :bind (:map grep-mode-map ("C-x C-q" . wgrep-change-to-wgrep-mode))) (use-package avy :ensure t :bind (("M-j" . avy-goto-char-timer) :map isearch-mode-map ("M-j" . avy-isearch)) :config (setopt avy-background t avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm"))) (use-package zzz-to-char :ensure t :bind (("M-z" . zzz-to-char))) (use-package anzu :ensure t :bind (("M-%" . anzu-query-replace-regexp) ("C-M-%" . anzu-query-replace))) (use-package isearch-mb :ensure t :config (setopt isearch-lazy-count t isearch-regexp-lax-whitespace t search-whitespace-regexp "\\W+" search-default-mode t ; Search regexp by default isearch-wrap-pause 'no) (define-advice isearch-cancel (:before (&rest _) add-search-to-history) "Add search string to history when canceling." (unless (equal "" isearch-string) (isearch-update-ring isearch-string isearch-regexp))) (define-advice perform-replace (:around (orig &rest r) no-anykey-exit) "Don't exit replace for any key that's not in `query-replace-map'." (save-window-excursion (cl-letf* ((lookup-key-orig (symbol-function 'lookup-key)) ((symbol-function 'lookup-key) (lambda (map key &optional accept-default) (or (apply lookup-key-orig map key accept-default) (when (eq map query-replace-map) 'help))))) (apply orig r)))) ;; Consult (autoload 'consult-line "consult" nil t) (autoload 'consult-isearch-history "consult" nil t) (add-to-list 'isearch-mb--after-exit #'consult-line) (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history) (keymap-set isearch-mb-minibuffer-map "M-s l" #'consult-line) (keymap-set isearch-mb-minibuffer-map "M-r" #'consult-isearch-history) ;; Anzu (autoload 'anzu-isearch-query-replace "anzu" nil t) (autoload 'anzu-isearch-query-replace-regexp "anzu" nil t) (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace) (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace-regexp) (keymap-set isearch-mb-minibuffer-map "M-%" #'anzu-isearch-query-replace-regexp) (keymap-set isearch-mb-minibuffer-map "C-M-%" #'anzu-isearch-query-replace) (isearch-mb-mode)) (use-package paredit :ensure t :hook ( emacs-lisp-mode-hook ielm-mode-hook eval-expression-minibuffer-setup-hook lisp-interaction-mode-hook lisp-mode-hook scheme-mode-hook fennel-mode-hook fennel-repl-mode-hook geiser-mode-hook geiser-repl-mode-hook) :config (keymap-set paredit-mode-map "C-j" (defun +paredit-newline () (interactive) (call-interactively (if (derived-mode-p 'lisp-interaction-mode) #'eval-print-last-sexp #'paredit-newline)))) (keymap-unset paredit-mode-map "RET" t) (keymap-unset paredit-mode-map "M-s" t) (keymap-unset paredit-mode-map "M-r" t) (add-to-list 'paredit-space-for-delimiter-predicates (defun paredit@dont-space-@ (endp delimiter) "Don't add a space after @ in `paredit-mode'." (let ((point (point))) (or endp (seq-every-p (lambda (prefix) (and (> point (length prefix)) (let ((start (- point (length prefix))) (end point)) (not (string= (buffer-substring start end) prefix))))) ;; Add strings to this list to inhibit adding a space ;; after them. '(",@"))))))) (use-package hungry-delete :ensure t :config (setopt hungry-delete-chars-to-skip " \t" hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip) hungry-delete-join-reluctantly nil) (add-to-list 'hungry-delete-except-modes 'eshell-mode) (add-to-list 'hungry-delete-except-modes 'nim-mode) (add-to-list 'hungry-delete-except-modes 'python-mode) ;; Keys (with-eval-after-load 'paredit (define-key paredit-mode-map [remap paredit-backward-delete] (defun paredit/hungry-delete-backward (arg) (interactive "*p") (if (looking-back hungry-delete-skip-regexp) (hungry-delete-backward (or arg 1)) (paredit-backward-delete arg)))) (define-key paredit-mode-map [remap paredit-forward-delete] (defun paredit/hungry-delete-forward (arg) (interactive "*p") (if (looking-at hungry-delete-skip-regexp) (hungry-delete-forward (or arg 1)) (paredit-forward-delete arg)))) ;; Mode (global-hungry-delete-mode))) (use-package macrostep :ensure t :after elisp-mode :bind ( :map emacs-lisp-mode-map ("C-c e" . macrostep-expand) :map lisp-interaction-mode-map ("C-c e" . macrostep-expand))) (use-package package-lint :ensure t) (use-package sly :ensure t :when inferior-lisp-program :preface (setopt inferior-lisp-program (choose-executable "sbcl")) (defun +sly-start-or-mrepl () (interactive) (if (ignore-errors (sly-connection)) (sly-mrepl (lambda (buf) (display-buffer-pop-up-window buf nil))) (call-interactively #'sly))) :config (autoload 'sly-mrepl "sly-mrepl" nil t) (keymap-set sly-mode-map "C-c C-z" #'+sly-start-or-mrepl) (setopt sly-net-coding-system 'utf-8-unix) (sly-symbol-completion-mode -1)) (use-package pdf-tools :ensure t :mode ("\\.[pP][dD][fF]\\'" . pdf-view-mode) :magic ("%PDF" . pdf-view-mode) :config (pdf-tools-install)) (use-package keychain-environment :ensure t :when (executable-find "keychain") :hook (after-init-hook . keychain-refresh-environment)) (use-package web-mode :ensure t :mode ("\\.phtml\\'" "\\.tpl\\.php\\'" "\\.[agj]sp\\'" "\\.as[cp]x\\'" "\\.erb\\'" "\\.mustache\\'" "\\.djhtml\\'" "\\.html?\\'") :config (add-hook 'web-mode-hook (defun web-mode@setup () (indent-tabs-mode -1)))) (use-package nginx-mode :ensure t :mode "/nginx/sites-\\(?:available\\|enabled\\)/") (use-package markdown-mode :ensure t :mode "\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'" :config (setopt markdown-command (choose-executable '("pandoc" "--from=markdown" "--to=html5") "markdown")) (add-hook 'markdown-mode-hook #'visual-fill-column-mode)) (use-package pandoc-mode :ensure t :hook ((markdown-mode-hook . pandoc-mode) (pandoc-mode-hook . pandoc-load-default-settings))) (use-package edit-indirect :ensure :bind (("C-c '" . edit-indirect-region))) (use-package transpose-frame :ensure t :bind (("C-x 5 t" . transpose-frame) ("C-x 5 h" . flop-frame) ; horizontal ("C-x 5 v" . flip-frame) ; vertical )) (use-package magit :pin melpa-stable :ensure t :bind ("C-x g" . magit)) (use-package git-modes :ensure t) (use-package eradio :ensure t :preface (defun eradio-toggle|play (&optional arg) "Run `eradio-toggle', or `eradio-play' with prefix ARG." (interactive "P") (if arg (eradio-play) (eradio-toggle))) :bind (("C-c r p" . eradio-toggle|play) ("C-c r s" . eradio-stop)) :config (setopt eradio-player '("mpv" "--no-video" "--no-terminal") eradio-channels ;; At some point I should actually ... write this in to this file or ;; something. But until I decide to quit using radish altogether, this ;; what I got. (with-current-buffer (find-file-noselect "~/etc/radish/stations") (let (chans) (dolist (line (string-split (buffer-substring-no-properties (point-min) (point-max)) "\n") chans) (unless (string-match-p "^#" line) (let* ((ll (string-split line "\t")) (url (cl-first ll)) (name (cl-second ll)) (tags (cl-third ll))) (when (and name (string-match-p "[^ \n\t]*://[^ \n\t]*" url)) (push (cons (format "%s - %s" name tags) url) chans))))))))) (use-package wiki-abbrev :after org ; Don't need abbrevs til I load org. :load-path "~/src/emacs/wiki-abbrev.el/" :config (setopt wiki-abbrev-file (etc/ "wiki-abbrevs")) (wiki-abbrev-insinuate)) (use-package flyspell-correct :ensure t :preface (defun +flyspell-correct-buffer (&optional prefix) "Run `flyspell-correct-wrapper' on all misspelled words in the buffer. With PREFIX, prompt to change the current dictionary." (interactive "P") (flyspell-buffer) (when prefix (let ((current-prefix-arg nil)) (call-interactively #'ispell-change-dictionary))) (flyspell-correct-move (point-min) :forward :rapid)) :after flyspell :bind (("" . +flyspell-correct-buffer) (:map flyspell-mode-map ("C-;" . flyspell-correct-wrapper))) :config (setq flyspell-correct--cr-key ";") (keymap-unset flyspell-mode-map "C-," t) (keymap-unset flyspell-mode-map "C-." t)) (use-package dired-subtree :ensure t :after dired :bind (:map dired-mode-map (("TAB" . dired-subtree-cycle) ("i" . dired-subtree-toggle)))) (use-package dired-hide-dotfiles ;; I could maybe use a more general package for this ... see ;; https://emacs.grym.io/#orgbbda609 :ensure t :bind (:map dired-mode-map ("." . dired-hide-dotfiles-mode))) (use-package dired-git-info :ensure t :bind (:map dired-mode-map (")" . dired-git-info-mode)) :config (setopt dgi-auto-hide-details-p nil)) (use-package expand-region ; needed for embrace anyway :ensure t :bind (("C-=" . er/expand-region))) (use-package embrace :ensure t :preface (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))))) (with-eval-after-load 'org (require 'embrace) (keymap-set org-mode-map "*" (org-insert-or-embrace "*")) (keymap-set org-mode-map "/" (org-insert-or-embrace "/")) (keymap-set org-mode-map "_" (org-insert-or-embrace "_")) (keymap-set org-mode-map "=" (org-insert-or-embrace "=")) (keymap-set org-mode-map "~" (org-insert-or-embrace "~")) (keymap-set org-mode-map "+" (org-insert-or-embrace "+"))) :bind (("C-\"" . embrace-commander)) :hook ((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))) (use-package apheleia :ensure t :config (setopt apheleia-hide-log-buffers t) (setf (alist-get 'shfmt apheleia-formatters) '("shfmt" "--case-indent")) (global-set-key (kbd "M-C-\\") (defun +apheleia-format|indent-buffer () (interactive) (if-let ((formatters (apheleia--get-formatters))) (apheleia-format-buffer formatters (lambda () (with-demoted-errors "Apheleia: %s" (when buffer-file-name (let ((apheleia--format-after-save-in-progress t)) (apheleia--save-buffer-silently))) (run-hooks 'apheleia-post-format-hook)))) (indent-region (point-min) (point-max)) (when buffer-file-name (save-buffer)))))) (use-package php-mode :ensure t) (use-package rec-mode :ensure t) ;; (use-package gauche-mode ;; :load-path "~/src/emacs/gauche-mode/" ;; :mode "\\.scm\\'" ;; :config ;; (setopt gauche-mode-info-language 'en ;; scheme-program-name "gosh -i" ;; scheme-compile-exp-command "%s") ;; (add-to-list 'scheme-source-modes 'gauche-mode) ;; (setf (alist-get "\\*scheme\\*" display-buffer-alist nil nil #'equal) ;; '((display-buffer-reuse-window ;; display-buffer-pop-up-window) ;; (inhibit-same-window . t))) ;; (add-hook 'gauche-mode-hook #'enable-gauche-paredit-mode) ;; (add-hook 'inferior-scheme-mode-hook #'enable-gauche-paredit-mode) ;; ;; Extras ;; (define-key gauche-mode-map (kbd "C-c C-k") ;; (defun +gauche-mode-send-buffer () ;; (interactive) ;; (save-mark-and-excursion ;; (let ((start (point-min)) ;; (end (point-max))) ;; (goto-char start) ;; (while (or (looking-at "#!") ; Shebang ;; (looking-at ":;") ; "shell trampoline" ;; ) ;; (forward-line 1) ;; (beginning-of-line) ;; (setq start (point))) ;; (scheme-send-region-and-go start end)))))) (use-package geiser :ensure t :config (use-package geiser-guile :ensure t) (use-package geiser-chicken :ensure t) (use-package geiser-chez :ensure t) (use-package geiser-gambit :ensure t) (use-package geiser-chibi :ensure t) (use-package macrostep-geiser :ensure t :config (eval-after-load 'geiser-mode '(add-hook 'geiser-mode-hook #'macrostep-geiser-setup)) (eval-after-load 'geiser-repl '(add-hook 'geiser-repl-mode-hook #'macrostep-geiser-setup))) (with-eval-after-load 'geiser-mode (keymap-set geiser-mode-map "C-c C-k" #'geiser-eval-buffer-and-go) (keymap-unset geiser-mode-map "C-." t) (keymap-unset scheme-mode-map "M-o" t))) (use-package treesit-auto ;; XXX: I don't really get the utility of this package, but I spent a ton of ;; time figuring out how to properly build it ... so here it is. :ensure t :when (treesit-available-p) :config ;; (setopt treesit-extra-load-path ;; ;; https://github.com/casouri/tree-sitter-module/ ;; `(,(expand-file-name "~/misc/tree-sitter-module/dist/"))) (setopt treesit-auto-install nil) ; javascript keeps failing (global-treesit-auto-mode)) (use-package elpher :ensure t) (use-package detached :when (executable-find "dtach") :ensure t :init (add-hook 'after-init-hook #'detached-init) :bind (([remap async-shell-command] . detached-shell-command) ([remap compile] . detached-compile) ([remap recompile] . detached-compile-recompile)) :config (setf detached-terminal-data-command system-type) (with-eval-after-load 'consult (global-set-key [remap detached-open-session] #'detached-consult-session))) (use-package lin :ensure t :config (setopt lin-face 'lin-cyan lin-mode-hooks '(dired-mode-hook ;; bongo-mode-hook ;; elfeed-search-mode-hook git-rebase-mode-hook grep-mode-hook ibuffer-mode-hook ilist-mode-hook ;; ledger-report-mode-hook log-view-mode-hook magit-log-mode-hook ;; mu4e-headers-mode-hook notmuch-search-mode-hook notmuch-tree-mode-hook occur-mode-hook org-agenda-mode-hook pdf-outline-buffer-mode-hook proced-mode-hook tabulated-list-mode-hook)) (lin-global-mode)) (use-package gcmh :ensure t :config (setopt gcmh-idle-delay 'auto gcmh-verbose nil) (gcmh-mode)) (use-package tmr :ensure t :preface (defun tmr-mode-line () (if (seq-find (lambda (tmr) (not (tmr--timer-finishedp tmr))) tmr--timers) (propertize "⏲" 'face 'font-lock-warning-face) "")) (add-to-list 'global-mode-string '("" (:eval (tmr-mode-line))) 'append)) ;; (use-package elfeed ;; :ensure t ;; :preface ;; ;; https://karthinks.com/software/lazy-elfeed/ ;; (defun elfeed-scroll-up-command (&optional arg) ;; "Scroll up or go to next feed item in Elfeed" ;; (interactive "^P") ;; (let ((scroll-error-top-bottom nil)) ;; (condition-case-unless-debug nil ;; (scroll-up-command arg) ;; (error (elfeed-show-next))))) ;; (defun elfeed-scroll-down-command (&optional arg) ;; "Scroll up or go to next feed item in Elfeed" ;; (interactive "^P") ;; (let ((scroll-error-top-bottom nil)) ;; (condition-case-unless-debug nil ;; (scroll-down-command arg) ;; (error (elfeed-show-prev))))) ;; :bind (("C-c f" . elfeed)) ;; :config ;; (setopt elfeed-enclosure-default-dir "~/var/download/" ;; elfeed-db-directory (sync/ "emacs/elfeed/db" t) ;; elfeed-curl-max-connections 4) ;; (add-hook 'elfeed-search-update-hook #'truncate-lines-local-mode) ;; (keymap-set elfeed-show-mode-map "SPC" #'elfeed-scroll-up-command) ;; (keymap-set elfeed-show-mode-map "S-SPC" #'elfeed-scroll-down-command) ;; (keymap-set elfeed-search-mode-map "a" #'elfeed-search-untag-all-unread) ;; (setq elfeed-feeds nil) ; Always reload the feed list from feeds.opml ;; (elfeed-load-opml "~/var/feeds.opml")) (use-package iedit ; XXX: is this necessary? :ensure t :init (customize-set-variable 'iedit-toggle-key-default (kbd "C-'"))) (use-package dumb-jump :ensure t :hook ((xref-backend-functions . dumb-jump-xref-activate))) (use-package le-thesaurus :ensure t :bind (("C-c w s" . le-thesaurus-get-synonyms) ("C-c w a" . le-thesaurus-get-antonyms))) (use-package devdocs :ensure t ;; not sure what to bind anything to yet ... so M-x it is ) (use-package comment-dwim-2 :ensure t :bind (("M-;" . comment-dwim-2) :map org-mode-map ("M-;" . org-comment-dwim-2)))