;;; 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 (setq 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 (setq 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 110))) (face-spec-set 'fixed-pitch `((t :family ,(find-font "Recursive Mono Linear Static" "Comic Code" "DejaVu Sans Mono") :height 1.0))) (face-spec-set 'variable-pitch `((t :family ,(find-font "Recursive Sans Casual Static" "Atkinson Hyperlegible" "DejaVu Serif") :height 1.0))) (face-spec-set 'font-lock-comment-face `((t :slant italic :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 ;;; TABS (setq tab-width 8 sh-indentation tab-width ) ;;; Hooks (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)))) (global-prettify-symbols-mode)) (use-package auth-source :config (setq 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 (setq 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) (setq 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 (setq 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 (setq password-cache t password-cache-expiry 3600)) (use-package time :config (setq 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 (setq tab-bar-show t tab-bar-close-button-show t) (setopt tab-bar-format `(tab-bar-format-history tab-bar-format-tabs tab-bar-separator tab-bar-format-add-tab tab-bar-format-align-right ,(defun tab-bar-extra-info () `((global menu-item ,(format-mode-line '((jabber-activity-mode jabber-activity-mode-string) (:eval (when (and (fboundp 'org-clocking-p) (org-clocking-p)) (format " %s" (truncate-string-to-width org-mode-line-string 16 nil nil (truncate-string-ellipsis))))) (:eval (tmr-mode-line)) (display-time-mode (:eval (format " %s" (string-trim display-time-string)))) ("" " "))) ignore)))) mode-line-misc-info (cl-delete-if (lambda (x) (eq (car x) 'global-mode-string)) mode-line-misc-info)) (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 (setq 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 (setq 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)) (use-package elec-pair :config (setopt electric-pair-skip-whitespace 'chomp) (electric-pair-mode)) (use-package bookmark :config (setopt bookmark-save-flag 1)) (use-package sh-script :config (sh-electric-here-document-mode -1)) (use-package cc-mode :config (setopt c-basic-offset 8)) ;;; 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 (setq +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/pulse-location.el/" :config (pulse-location-mode)) (use-package emacs ; `modus-themes' isn't a package ... :config (setopt modus-themes-mixed-fonts t) (add-hook 'modus-themes-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))))) (add-hook 'after-init-hook (defun modus@load () (+reset-faces) (pcase (string-trim (shell-command-to-string "darkman get")) ("light" (load-theme 'modus-operandi t)) ("dark" (load-theme 'modus-vivendi t)))))) (use-package electric-cursor :load-path "~/src/electric-cursor.el/" :config (setq electric-cursor-alist '((overwrite-mode . box) (t . bar))) (electric-cursor-mode)) (use-package mode-line-bell :load-path "~/src/mode-line-bell.el/" :config (setq mode-line-bell-flash-time 0.25) (mode-line-bell-mode)) (use-package titlecase :load-path "~/src/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/scule.el/" :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/filldent.el/" :bind ("M-q" . filldent-dwim) :config (setq filldent-fill-modes '(web-mode))) (use-package frowny :load-path "~/src/frowny.el/" :config (global-frowny-mode)) (use-package keepassxc-shim :load-path "~/src/keepassxc-shim.el/" :config (keepassxc-shim-activate)) (use-package hippie-completing-read :load-path "~/src/hippie-completing-read.el/" :bind (("M-/" . hippie-completing-read))) ;;; External packages (use-package async :ensure t :config ;; https://github.com/jwiegley/emacs-async/issues/64 ;; (setq 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 ;; (setq 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 (setq 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 (setq 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 (setq 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 (setq 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 (setq 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) (setq 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 (setq 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 (setq eradio-player '("mpv" "--no-video" "--no-terminal") eradio-channels ;; (name . url) '(("Nightwave Plaza" . "http://radio.plaza.one/ogg") ("Radio Paradise - Main Mix" . "http://stream.radioparadise.com/rp_192m.ogg") ("Radio Paradise - Mellow Mix" . "http://stream.radioparadise.com/mellow-96m.ogg") ("Radio Paradise - Rock Mix" . "http://stream.radioparadise.com/rock-96m.ogg") ("Radio Paradise - Global Mix" . "http://stream.radioparadise.com/global-96m.ogg") ("KLSU" . "http://130.39.238.143:8010/stream.mp3")) ;; 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/wiki-abbrev.el/" :config (setq 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 (setq 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 (setq 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 geiser :ensure t :config (when (executable-find "guile") (use-package geiser-guile :ensure t)) (when (executable-find "chicken") (use-package geiser-chicken :ensure t)) (when (or (prog1 (executable-find "chez") (setopt geiser-chez-binary (executable-find "chez"))) (executable-find "petite") (executable-find "scheme")) (use-package geiser-chez :ensure t)) (when (executable-find "gambit") (use-package geiser-gambit :ensure t)) (when (executable-find "chibi-scheme") (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 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 (setq 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 (setq 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 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)))