emacs/init.el

908 lines
30 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; init.el -*- lexical-binding: t; coding: utf-8-unix -*-
;; Author: Case Duckworth <acdw@acdw.net>
;; Created: Sometime during Covid-19, 2020
;; Keywords: configuration
;; URL: https://tildegit.org/acdw/emacs
;; Bankruptcy: 6
;; This file is NOT part of GNU Emacs.
;;;- License:
;; Everyone is permitted to do whatever with this software, without
;; limitation. This software comes without any warranty whatsoever,
;; but with two pieces of advice:
;; - Don't hurt yourself.
;; - Make good choices.
;;;- Code:
;;; `setup' -- configuration macro
(straight-use-package '(setup :host nil
:repo "https://git.sr.ht/~zge/setup"))
(require 'setup)
;;;; shorthand for `customize-set-variable' (via setup)
(defmacro setc (&rest args)
"Customize user options using ARGS like `setq'."
(declare (debug setq))
`(setup (:option ,@args)))
;;;; Install packages with `straight-use-package'
(setup-define :straight
(lambda (recipe)
`(straight-use-package ',recipe))
:documentation "Install RECIPE with `straight-use-package'."
:repeatable t
:shorthand (lambda (sexp)
(let ((recipe (cadr sexp)))
(if (consp recipe)
(car recipe)
recipe))))
;;;; Set options using `setq-default', instead of `customize-set-variable'
;; From what I can tell, `customize-set-variable' loads "all the dependencies
;; for each SYMBOL it sets (see `custom-load-symbol'). Since I don't want to do
;; that all the time, here's `:set'. DON'T USE THIS HARDLY EVER. Honestly, I
;; might want to do a `:option-after' instead (with `:after-loaded' set to t)...
(setup-define :setq-default
(lambda (variable value)
`(setq-default ,variable ,value))
:documentation "Set options with `setq-default'. USE SPARINGLY!"
:debug '(sexp form)
:repeatable t)
;;;; Bind keys to `acdw/leader'
(setup-define :acdw/leader
(lambda (key command)
`(progn
(autoload #',command (symbol-name setup-name))
(define-key acdw/leader
,(if (stringp key) (kbd key) key)
#',command)))
:documentation "Bind KEY to COMMAND in `acdw/leader' map."
:debug '(form sexp)
:repeatable t)
;;;; Bind keys, and autoload the functions they're bound to.
(setup-define :bind-autoload
(lambda (key command)
`(progn
(autoload #',command (symbol-name setup-name))
(define-key (symbol-value setup-map)
,(if (stringp key) (kbd key) key)
#',command)))
:documentation "Bind KEY to COMMAND, and autload COMMAND from FEATURE."
:debug '(form sexp)
:repeatable t)
;;; Good defaults
;;;; About me
(setc user-full-name "Case Duckworth"
user-mail-address "acdw@acdw.net"
calendar-location-name "Baton Rouge, LA"
calendar-latitude 30.4
calendar-longitude -91.1)
;;;; Lines
(setc fill-column 80
word-wrap t
truncate-lines nil)
(add-hook 'text-mode-hook #'turn-on-auto-fill)
(global-display-fill-column-indicator-mode +1)
(global-so-long-mode +1)
;; Only fill comments in prog-mode.
(add-hook 'prog-mode-hook
(defun hook--auto-fill-prog-mode ()
(setq-local comment-auto-fill-only-comments t)
(turn-on-auto-fill)))
;; Don't truncate lines in the minibuffer.
(add-hook 'minibuffer-setup-hook
(defun hook--minibuffer-enable-truncate-lines ()
(setq-local truncate-lines t)))
;;;; Whitespace
(setc whitespace-style
'(empty indentation space-before-tab space-after-tab)
indent-tabs-mode nil
tab-width 4
smie-indent-basic tab-width)
(add-hook 'before-save-hook #'whitespace-cleanup)
;;;; Pairs
(setc show-paren-delay 0
show-paren-style 'mixed
show-paren-when-point-inside-paren t
show-paren-when-point-in-periphery t)
(add-hook 'prog-mode-hook #'electric-pair-local-mode)
;;;; Killing and yanking
(setc save-interprogram-paste-before-kill t
yank-pop-change-selection t
x-select-enable-clipboard t
x-select-enable-primary t
mouse-drag-copy-region t
kill-do-not-save-duplicates t)
(delete-selection-mode +1)
;;;; Encoding
(setc local-coding-system 'utf-8-unix
coding-system-for-read 'utf-8-unix
coding-system-for-write 'utf-8-unix
buffer-file-coding-system 'utf-8-unix
org-export-coding-system 'utf-8-unix
org-html-coding-system 'utf-8-unix
default-process-coding-system '(utf-8-unix . utf-8-unix)
x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
(set-charset-priority 'unicode)
(set-language-environment "UTF-8")
(prefer-coding-system 'utf-8-unix)
(set-default-coding-systems 'utf-8-unix)
(set-terminal-coding-system 'utf-8-unix)
(set-keyboard-coding-system 'utf-8-unix)
(set-selection-coding-system 'utf-8-unix)
;;;; Uniquify
(setup (:require uniquify)
(:option uniquify-buffer-name-style 'forward
uniquify-separator path-separator
uniquify-after-kill-buffer-p t
uniquify-ignore-buffers-re "^\\*"))
;;;; Files
(setc backup-directory-alist `((".*" . ,(acdw/in-dir "backup/" t)))
tramp-backup-directory-alist backup-directory-alist
auto-save-file-name-transforms `((".*" ,(acdw/in-dir "auto-save/" t) t))
auto-save-list-file-prefix (acdw/in-dir "auto-save-list/.saves-" t)
backup-by-copying t
delete-old-versions t
version-control t
vc-make-backup-files t)
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
;;;;; Auto-save files
(auto-save-visited-mode +1)
;; And /actually/ save all buffers when unfocused
(add-function :after after-focus-change-function
(defun hook--auto-save-when-unfocused ()
"Save all buffers when out of focus."
(acdw/when-unfocused #'save-some-buffers t)))
;;;;; Autorevert files
(setup (:require autorevert)
(global-auto-revert-mode +1))
;;;;; Save place in files
(setup (:require saveplace)
(:option save-place-file (acdw/in-dir "places.el")
save-place-forget-unreadable-files (eq acdw/system :home))
(save-place-mode +1))
;;;;; Keep track of recent files
(setup (:require recentf)
(:option recentf-save-file (acdw/in-dir "recentf.el")
recentf-max-menu-items 100
recentf-max-saved-items nil
recentf-auto-cleanup 60
(append recentf-exclude) acdw/dir)
(recentf-mode +1))
;;;; Windows
(winner-mode +1)
;;;; Minibuffer
(setc minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt)
enable-recursive-minibuffers t
file-name-shadow-properties '(invisible t intangible t)
read-answer-short t)
(minibuffer-depth-indicate-mode +1)
(file-name-shadow-mode +1)
(fset 'yes-or-no-p #'y-or-n-p)
;;;;; Save minibuffer command history (and others)
(setup (:require savehist)
(:option (append savehist-additional-variables) 'kill-ring
(append savehist-additional-variables) 'search-ring
(append savehist-additional-variables) 'regexp-search-ring
history-length t
history-delete-duplicates t
savehist-autosave-interval 6
savehist-file (acdw/in-dir "savehist.el"))
(savehist-mode +1))
;;;;; Completion framework
(setup (:require icomplete)
(:option completion-ignore-case t
read-buffer-completion-ignore-case t
icomplete-delay-completions-threshold 0
icomplete-max-delay-chars 0
icomplete-compute-delay 0
icomplete-show-matches-on-no-input t
icomplete-with-buffer-completion-tables t
icomplete-in-buffer t
completion-styles '(partial-completion substring flex)
completion-category-defaults nil
completion-category-overrides
'((file (styles . (partial-completion)))))
(fido-mode -1)
(icomplete-mode +1))
;;;;; `imenu'
(setup imenu
(:option imenu-auto-rescan t))
;;;; Cursor
(setc cursor-type 'bar
cursor-in-non-selected-windows 'hollow)
;;;; Scrolling
(setc auto-window-vscroll nil
fast-but-imprecise-scrolling t
scroll-margin 0
scroll-conservatively 101
scroll-preserve-screen-position 1)
;;;; Fonts
;; Load them /after/ the first frame comes into focus
(add-function :after after-focus-change-function
(defun hook--setup-fonts ()
(dolist (face '(default fixed-pitch))
;; `default' and `fixed-pitch' should be the same.
(set-face-attribute face nil
:font (pcase acdw/system
(:home "DejaVu Sans Mono-10")
(:work "Consolas-10")
(:other "monospace-10"))))
;; `variable-pitch' is, of course, different.
(set-face-attribute 'variable-pitch nil
:font (pcase acdw/system
(:home "DejaVu Sans")
(:work "Calibri-11")
(:other "sans-serif")))
(remove-function after-focus-change-function
'hook--setup-fonts)))
;;;; Debugger
(setup debugger
(:hook visual-line-mode)
(:acdw/leader "d" toggle-debug-on-error))
;;;; Garbage collection
(add-hook 'minibuffer-setup-hook #'acdw/gc-disable)
(add-hook 'minibuffer-exit-hook #'acdw/gc-enable)
(add-function :after after-focus-change-function
(defun hook--gc-when-unfocused ()
(acdw/when-unfocused #'garbage-collect)))
;;;; Spelling
(setup flyspell
(setenv "LANG" "en_US")
(:option ispell-program-name "hunspell"
ispell-dictionary "en_US"
ispell-personal-dictionary "~/.hunspell_personal")
(ispell-set-spellchecker-params)
(unless (file-exists-p ispell-personal-dictionary)
(write-region "" nil ispell-personal-dictionary nil 0))
(:needs ispell-program-name)
;; add hooks
(add-hook 'text-mode-hook #'flyspell-mode)
(add-hook 'prog-mode-hook #'flyspell-prog-mode))
;;;; MS Windows
(when (eq acdw/system :work)
(setc w32-allow-system-shell t
w32-pass-lwindow-to-system nil
w32-lwindow-modifier 'super
w32-pass-rwindow-to-system nil
w32-rwindow-modifier 'super
w32-pass-apps-to-system nil
w32-apps-modifier 'hyper))
;;;; Set up non-special modes
;; Great idea from brause.cc
(defun-with-hooks '(text-mode-hook prog-mode-hook diff-mode-hook)
(defun hook--setup-regular-modes ()
(setq indicate-empty-lines t
indicate-buffer-boundaries '((top . right) (bottom . right)))
(goto-address-mode +1)
(show-paren-mode +1)))
;;;; Etc. good defaults
(setc custom-file (acdw/in-dir "custom.el")
inhibit-startup-screen t
initial-buffer-choice t
initial-scratch-message (concat ";; Howdy, " (nth 0 (split-string
user-full-name))
"! Welcome to GNU Emacs.\n\n")
default-directory (expand-file-name "~/")
disabled-command-function nil
load-prefer-newer t
comp-async-report-warnings-errors nil
frame-title-format '("%b %+%* GNU Emacs"
(:eval (when (frame-parameter nil 'client)
" Client")))
tab-bar-show 1
use-dialog-box nil
use-file-dialog nil
echo-keystrokes 0.25
recenter-positions '(top middle bottom)
attempt-stack-overflow-recovery nil
attempt-orderly-shutdown-on-fatal-signal nil
window-resize-pixelwise t
find-function-C-source-directory
(pcase acdw/system
(:work (expand-file-name (concat "~/src/emacs-"
emacs-version
"/src")))
(:home (expand-file-name "~/src/pkg/emacs/src/emacs-git/src"))
(:other nil)))
;;;; Etc. modes
(tooltip-mode -1)
;;;; Etc. bindings
(global-set-key [remap just-one-space] #'cycle-spacing)
(global-set-key (kbd "M-/") #'hippie-expand)
(global-set-key (kbd "M-=") #'count-words)
(global-set-key (kbd "C-x C-b") #'ibuffer)
;;; Applications
;; Some of these are built-in, some are packages; all are "extra" functionality
;; in Emacs (i.e., they're not /just/ editing text)
;;;; Org mode
(setup (:straight (org :host nil
:repo "https://code.orgmode.org/bzg/org-mode.git"))
(require 'acdw-org)
(:option org-directory "~/org"
org-hide-emphasis-markers t
org-fontify-whole-heading-line t
org-fontify-done-headline t
org-fontify-quote-and-verse-blocks t
org-src-fontify-natively t
org-pretty-entities t
org-tags-column (- 0 fill-column -3)
org-src-tab-acts-natively t
org-src-window-setup 'current-window
org-confirm-babel-evaluate nil
org-adapt-indentation nil
org-catch-invisible-edits 'smart
org-special-ctrl-a/e t
org-special-ctrl-k t
org-imenu-depth 3
org-export-headline-levels 8
org-export-with-smart-quotes t
org-export-with-sub-superscripts t)
(:bind "RET" unpackaged/org-return-dwim)
(add-hook 'before-save-hook #'acdw/hook--org-mode-fix-blank-lines))
;;;; Eshell
(setup eshell
(defun eshell-quit-or-delete-char (arg)
"Delete the character to the right, or quit eshell on an empty line."
(interactive "p")
(if (and (eolp) (looking-back eshell-prompt-regexp))
(eshell-life-is-too-much)
(delete-forward-char arg)))
(:option eshell-directory-name (acdw/in-dir "eshell/" t)
eshell-aliases-file (acdw/in-dir "eshell/aliases" t))
(add-hook 'eshell-mode-hook
(defun hook--eshell-setup ()
"Stuff to do after eshell is done setting up."
(define-key eshell-mode-map (kbd "C-d")
#'eshell-quit-or-delete-char)
(setq mode-line-format '(:eval simple-modeline--mode-line)))))
;;;; Ediff
(setup ediff
(:option ediff-window-setup-function 'ediff-setup-windows-plain
ediff-split-window-function 'split-window-horizontally))
;;;; Web browsing
(setup browse-url
(:setq-default browse-url-browser-function 'eww-browse-url
browse-url-secondary-browser-function
(if (executable-find "firefox")
'browse-url-firefox
'browse-url-default-browser)
browse-url-new-window-flag t
browse-url-firefox-new-window-is-tab t)
(when (eq acdw/system :work)
(add-to-list 'exec-path "C:/Program Files/Mozilla Firefox")))
(setup shr
(:option shr-width fill-column
shr-max-width fill-column
shr-max-image-proportion 0.6
shr-image-animate t
shr-discard-aria-hidden t))
(setup eww
(:hook acdw/reading-mode))
;;;; Gemini/gopher browsing
(setup (:straight (elpher :host nil
:repo "git://thelambdalab.xyz/elpher.git"))
(:option elpher-ipv4-always t
elpher-certificate-directory (acdw/in-dir "elpher/")
elpher-gemini-max-fill-width fill-column)
(:bind "n" elpher-next-link
"p" elpher-prev-link
"o" elpher-follow-current-link
"G" elpher-go-current)
(:hook acdw/reading-mode)
;; Make `eww' gemini/gopher aware. From Emacswiki.
(advice-add 'eww-browse-url :around
(defun elpher:eww-browse-url (original url &optional new-window)
"Handle gemini and gopher links."
(cond ((string-match-p "\\`\\(gemini\\|gopher\\)://" url)
(require 'elpher)
(elpher-go url))
(t (funcall original url new-window))))))
(setup (:straight (gemini-mode
:host nil
:repo "https://git.carcosa.net/jmcbray/gemini.el.git"))
(:option (append auto-mode-alist)
'("\\.\\(gemini\\|gmi\\)\\'" . gemini-mode)))
;;;; File browsing
(setup dired
(:setq-default dired-recursive-copies 'always
dired-recursive-deletes 'always
delete-by-moving-to-trash t
dired-listing-switches "-Al"
ls-lisp-dirs-first t
dired-ls-F-marks-symlinks t
dired-no-confirm '(byte-compile
chgrp chmod chown copy
hardlink load move
shell touch symlink)
dired-dwim-target t)
(:also-load dired-x)
(:hook dired-hide-details-mode
hl-line-mode)
(:global "C-x C-j" dired-jump)
(:bind "RET" dired-find-alternate-file)
(with-eval-after-load 'dired
(setup (:straight dired-subtree)
(define-key dired-mode-map "i" #'dired-subtree-toggle))
(setup (:straight dired-collapse)
(:hook-into dired-mode))
(setup (:straight trashed)
(:option trashed-action-confirmer 'y-or-n-p))))
;;;; Magit
(setup (:straight magit)
(:acdw/leader "g" magit-status)
(:option magit-display-buffer-function
(defun magit-display-buffer-same-window (buffer)
"Display BUFFER in the selected window like God intended."
(display-buffer buffer '(display-buffer-same-window)))
magit-popup-display-buffer-action '((display-buffer-same-window))))
;;;; Read e-books (nov.el)
(setup (:straight nov)
(:option nov-text-width fill-column
(append auto-mode-alist) '("\\.epub\\'" . nov-mode)))
;;;; PDF Tools
(when (eq acdw/system :home)
(setup (:straight pdf-tools)
(pdf-loader-install)))
;;;; VTerm
(when (eq acdw/system :home)
(setup (:straight vterm)))
;;; Packages
;;;; Interactivity
;;;;; Begin-end
(setup (:straight beginend)
(beginend-global-mode +1))
;;;;; MWIM
(setup (:straight mwim)
(:global "C-a" mwim-beginning
"C-e" mwim-end))
;;;;; Expand-region
(setup (:straight expand-region)
(:global "C-=" er/expand-region))
;;;;; CRUX
(setup (:straight crux)
(:global "M-o" crux-other-window-or-switch-buffer
"C-k" crux-kill-and-join-forward
"C-o" crux-smart-open-line-above
"C-S-o" crux-smart-open-line
"C-M-\\" crux-cleanup-buffer-or-region
"C-c i" crux-find-user-init-file)
(crux-reopen-as-root-mode +1))
;;;;; AVY ... & friends
(setup (:straight avy)
(:global "C-:" avy-goto-char
"C-'" avy-goto-char-timer
"M-g f" avy-goto-line
"M-g w" avy-goto-word-1
"C-c C-j" avy-resume)
(eval-after-load "isearch"
'(define-key isearch-mode-map (kbd "C-'") #'avy-isearch)))
;;;;; zzz-to-char
(setup (:straight zzz-to-char)
(defun acdw/zzz-up-to-char (prefix)
"Call `zzz-up-to-char', unless issued a PREFIX, in which case
call `zzz-to-char'."
(interactive "P")
(if prefix
(call-interactively #'zzz-to-char)
(call-interactively #'zzz-up-to-char)))
(:global "M-z" acdw/zzz-up-to-char))
;;;;; anzu
(setup (:straight anzu)
(:option anzu-replace-to-string-separator ""
anzu-cons-mode-line-p nil)
(add-to-list 'mode-line-misc-info '(:eval (anzu--update-mode-line)))
(:global [remap query-replace] anzu-query-replace
[remap query-replace-regexp] anzu-query-replace-regexp)
(:with-map isearch-mode-map
(:bind [remap isearch-query-replace] anzu-isearch-query-replace
[remap isearch-query-replace-regexp]
anzu-isearch-query-replace-regexp))
(global-anzu-mode +1))
;;;;; smart hungry delete
(setup (:straight smart-hungry-delete)
(:global "<backspace>" smart-hungry-delete-backward-char
"C-d" smart-hungry-delete-forward-char)
(smart-hungry-delete-add-default-hooks))
;;;; Functionality
;;;;; Async
(setup (:straight async)
(autoload 'dired-async-mode "dired-async.el" nil t)
(dired-async-mode +1))
;;;;; Undo-fu
(setup (:straight undo-fu)
(:global "C-/" undo-fu-only-undo
"C-?" undo-fu-only-redo))
(setup (:straight undo-fu-session)
(:option undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'"
"/git-rebase-todo\\'")
undo-fu-session-directory (acdw/in-dir "undo/" t)
undo-fu-session-compression (eq acdw/system :home))
(global-undo-fu-session-mode +1))
;;;; Minibuffer
(setup (:straight (vertico
:host github
:repo "minad/vertico"))
(advice-add #'completing-read-multiple :filter-args
(defun crm-indicator (args)
(cons (concat "[CRM] " (car args)) (cdr args))))
(icomplete-mode -1)
(vertico-mode +1))
;;;;; Orderless
(setup (:straight orderless)
(:option (prepend completion-styles) 'orderless))
;;;;; Consult
(setup (:straight consult)
;; "Sensible" functions
(defun consult-sensible-grep ()
"Perform `consult-git-grep' if in a git project, otherwise `consult-ripgrep'
if ripgrep is installed, otherwise `consult-grep'."
(interactive "P")
(cond ((= (vc-backend buffer-file-name) "Git")
(call-interactively #'consult-git-grep))
((executable-find "rg")
(call-interactively #'consult-ripgrep))
(t (call-interactively #'consult-grep))))
(defun consult-sensible-find ()
"Peform `consult-locate' if locate is installed, otehrwise `consult-find'."
(interactive "P")
(cond ((executable-find "locate") (call-interactively #'consult-locate))
(t (call-interactively #'consult-find))))
;; Bindings
(:global
;; C-c bindings (`mode-specific-map')
"C-c h" consult-history
"C-c m" consult-mode-command
"C-c b" consult-bookmark
"C-c k" consult-kmacro
;; C-x bindings (`ctl-x-map')
"C-x M-:" consult-complex-command
"C-x b" consult-buffer
"C-x 4 b" consult-buffer-other-window
"C-x 5 b" consult-buffer-other-frame
;; Custom M-# bindings for fast register access
"M-#" consult-register-load
"M-'" consult-register-store
"C-M-#" consult-register
;; M-g bindings (`goto-map')
"M-g e" consult-compile-error
"M-g g" consult-goto-line
"M-g M-g" consult-goto-line
"M-g o" consult-outline
"M-g m" consult-mark
"M-g k" consult-global-mark
"M-g i" consult-imenu
"M-g I" consult-project-imenu
;; M-s bindings (`search-map')
"M-s g" consult-sensible-grep
"M-s f" consult-sensible-find
"M-s l" consult-line
"M-s m" consult-multi-occur
"M-s k" consult-keep-lines
"M-s u" consult-focus-lines
;; Other bindings
"M-y" consult-yank-pop
"<help> a" consult-apropos
;; Isearch integration
"M-s e" consult-isearch)
(:with-map isearch-mode-map
(:bind "M-e" consult-isearch
"M-s e" consult-isearch
"M-s l" consult-line))
;; Registers
(autoload 'consult-register-preview "consult")
(:option register-preview-delay 0
register-preview-function #'consult-register-format)
(:advise register-preview :override #'consult-register-window)
;; Xref
(:option xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
;; Projects
(:option consult-project-root-function #'vc-root-dir))
;;;;; Marginalia
(setup (:straight marginalia)
(:option marginalia-annotators '(marginalia-annotators-heavy
marginalia-annotators-light))
(marginalia-mode +1))
;;;; UI
;;;;; Modus themes
(setup (:straight (modus-themes
:host gitlab
:repo "protesilaos/modus-themes"))
(:option modus-themes-slanted-constructs t
modus-themes-bold-constructs t
modus-themes-region 'bg-only
modus-themes-org-blocks 'grayscale
modus-themes-headings '((1 . section)
(t . no-color))
modus-themes-mode-line nil)
(acdw/sunrise-sunset #'modus-themes-load-operandi
#'modus-themes-load-vivendi))
;;;;; Mode line
(setup (:straight simple-modeline)
(setup (:straight minions))
(:option simple-modeline-segments
'((acdw-modeline/modified
acdw-modeline/buffer-name
acdw-modeline/vc-branch
simple-modeline-segment-position
simple-modeline-segment-word-count)
(simple-modeline-segment-misc-info
simple-modeline-segment-process
acdw-modeline/god-mode-indicator
acdw-modeline/minions
simple-modeline-segment-major-mode)))
(require 'acdw-modeline)
(simple-modeline-mode +1))
;;;;; Olivetti
;; also useful for `acdw/reading-mode'
(setup (:straight olivetti)
(:option olivetti-body-width (+ fill-column 4)
olivetti-minimum-body-width fill-column))
;;;;; Outshine
(setup (:straight outshine)
(:option outline-minor-mode-prefix "")
(:hook-into emacs-lisp-mode))
;;;;; Form-feed
(setup (:straight form-feed)
(global-form-feed-mode +1))
;;;;; Which-key
(setup (:straight which-key)
(:option which-key-show-early-on-C-h t
which-key-idle-delay 10000
which-key-idle-secondary-delay 0.05)
(which-key-setup-side-window-bottom)
(which-key-mode +1))
;;;;; Helpful
(setup (:straight helpful)
(:global "<help> f" helpful-callable
"<help> v" helpful-variable
"<help> k" helpful-key
"<help> o" helpful-symbol
"C-c C-d" helpful-at-point))
;;;; Utilities
;;;;; 0x0 -- upload files to a nullpointer
(setup (:straight (0x0 :host nil
:repo "https://git.sr.ht/~zge/nullpointer-emacs"))
(:option 0x0-default-host 'ttm))
;;;;; Flyspell-correct
(when (executable-find ispell-program-name)
(with-eval-after-load 'flyspell
(setup (:straight flyspell-correct)
(define-key flyspell-mode-map (kbd "C-;") #'flyspell-correct-wrapper))))
;;;; System tie-ins
;; Insctead of using `setup''s `:needs', I'm going to wrap these in
;; `executable-find' forms. I don't want to waste time with pulling packages
;; that won't work on a machine.
;;;;; PKGBUILDs
(when (eq acdw/system :home)
(setup (:straight pkgbuild-mode)))
;;; Programming languages
;; This section includes packages and other settings, because most languages'
;; packages aren't packaged with Emacs.
;;;; Formatting
(setup (:straight (apheleia :host github
:repo "raxod502/apheleia"))
(apheleia-global-mode +1)
;; Use a dumb formatter on modes that `apheleia' doesn't work for.
(add-hook 'before-save-hook
(defun dumb-auto-format ()
"Run `indent-region' in buffers that don't have an `apheleia'
formatter set."
(unless (and (fboundp 'apheleia--get-formatter-command)
(apheleia--get-formatter-command))
(indent-region (point-min) (point-max))))))
;;;; Eldoc
(setup eldoc
(:option eldoc-idle-delay 0.1
eldoc-echo-area-use-multiline-p nil))
;;;; Lisps
(setup (:straight paren-face)
(global-paren-face-mode +1))
;;;;; Paredit mode
(setup (:straight paredit)
(autoload 'enable-paredit-mode "paredit" nil t)
(dolist (hook '(emacs-lisp-mode-hook
eval-expression-minibuffer-setup-hook
ielm-mode-hook
lisp-mode-hook
lisp-interaction-mode-hook
scheme-mode-hook))
(add-hook hook #'enable-paredit-mode))
(add-hook 'paredit-mode-hook
(defun hook--paredit-disable-electric-pair ()
(electric-pair-local-mode -1)))
(require 'eldoc)
(eldoc-add-command 'paredit-backward-delete 'paredit-close-round)
(:bind "<backspace>" paredit-backward-delete))
;;;;; Emacs lisp
(setup elisp-mode
(:option eval-expression-print-length nil
eval-expression-print-level nil
lisp-indent-function #'lisp-indent-function)
(defun acdw/eval-region-or-buffer ()
(interactive)
(if (region-active-p)
(eval-region (region-beginning) (region-end))
(eval-buffer)))
(:with-map emacs-lisp-mode-map
(:bind "C-c C-c" acdw/eval-region-or-buffer
"C-c C-z" ielm))
(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
(add-hook 'ielm-mode-hook 'turn-on-eldoc-mode))
(setup (:straight macrostep)
(define-key emacs-lisp-mode-map (kbd "C-c e") #'macrostep-expand))
(setup (:straight eros)
(:hook-into emacs-lisp-mode))
;;;;; Fennel
(when (executable-find "fennel")
(setup (:straight fennel-mode)
(autoload 'fennel-mode "fennel-mode" nil t)
(:option (append auto-mode-alist) '("\\.fnl\\'" . fennel-mode))
(:bind "C-c C-c" )))
;;;;; Scheme
(when (or (executable-find "guile")
(executable-find "csi")
(executable-find "racket"))
(setup (:straight geiser)
(:with-mode geiser-repl-mode
(:hook enable-paredit-mode))))
;;;; Lua
(setup (:straight lua-mode)
(:option (append auto-mode-alist) '("\\.lua\\'" . lua-mode)))
;;;; Shell scripts
(setup sh-mode
(:option sh-basic-offset tab-width
sh-indent-after-case 0
sh-indent-for-case-alt '+
sh-indent-for-case-label 0)
(:local-set indent-tabs-mode t)
(when (executable-find "shfmt")
(with-eval-after-load 'apheleia
(:option (append apheleia-formatters) '(shfmt . ("shfmt"))
(append apheleia-mode-alist) '(sh-mode . shfmt))))
(when (executable-find "shellcheck")
(straight-use-package 'flymake-shellcheck)
(:hook flymake-mode
flymake-shellcheck-load)))
;;;; Web languages
(setup (:straight web-mode)
(:option css-level-offset 2
js-indent-level 2
sgml-indent-offset 2)
(dolist (extension '("\\(p\\|dj\\)?html"
"html?"
"\\(tpl\\.\\)?php"
"[agj]sp"
"as[cp]x"
"erb"
"mustache"))
(add-to-list 'auto-mode-alist
`(,(concat "\\." extension "\\'") . web-mode))))
;;;; FORTH
(when (locate-library "gforth")
(autoload 'forth-mode "gforth")
(add-to-list 'auto-mode-alist '("\\.fs\\'" . forth-mode))
(autoload 'forth-block-mode "gforth")
(add-to-list 'auto-mode-alist '("\\.fb\\'" . forth-block-mode)))
;;;- init.el ends here