summaryrefslogtreecommitdiff
path: root/.config/emacs/init.el
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2024-10-16 22:04:33 +0200
committerThomas Voss <mail@thomasvoss.com> 2024-10-16 22:04:33 +0200
commit0ee7fa9c382ae30295f0b8d88457f7856c7ff800 (patch)
tree6b5a0cf01fa0bfa4d01b0134b268f5d993055c0b /.config/emacs/init.el
parentd452ae1347b3711bc0a7ac80cfa2c37d9d63836e (diff)
emacs: Overhaul configuration completely
Diffstat (limited to '.config/emacs/init.el')
-rw-r--r--.config/emacs/init.el1160
1 files changed, 246 insertions, 914 deletions
diff --git a/.config/emacs/init.el b/.config/emacs/init.el
index f801eba..778fc82 100644
--- a/.config/emacs/init.el
+++ b/.config/emacs/init.el
@@ -1,965 +1,297 @@
-;;; init.el --- Emacs configuration file -*- lexical-binding: t; -*-
-
- ;;; Preamble
-(x-set user-full-name "Thomas Voss"
- user-mail-address "mail@thomasvoss.com")
-
-(when (< emacs-major-version 29)
- (error "Emacs 29 or newer is required"))
-
-(when (native-comp-available-p)
- (x-set
- native-comp-async-report-warnings-errors nil
- native-comp-verbose 0
- native-comp-debug 0
- native-comp-jit-compilation t
- native-compile-prune-cache t))
-
-(require 'package)
-(x-set
- package-archives (let ((protocol (if (gnutls-available-p) "https" "http")))
- (mapcar
- (lambda (pair)
- (cons (car pair) (concat protocol "://" (cdr pair))))
- '(("gnu" . "elpa.gnu.org/packages/")
- ("nongnu" . "elpa.nongnu.org/nongnu/")
- ("melpa" . "melpa.org/packages/"))))
- package-archive-priorities '(("gnu" . 3)
- ("nongnu" . 2)
- ("melpa" . 1))
- package-user-dir (expand-file-name "pkg" x-data-directory))
-(package-initialize)
-
-(eval-and-compile
- (x-set
- use-package-always-defer t
- use-package-always-ensure t
- use-package-expand-minimally t))
-
-(eval-when-compile
- (require 'use-package))
-
- ;;; Convenience Macros and -Functions
-(defmacro λ (&rest body)
- "Convenience macro to create lambda functions that take no arguments
-with much shorter and concise syntax. Calling ‘λ’ with BODY is
-equivalent to calling ‘lambda’ with an empty argument list and BODY."
- (declare (pure t) (side-effect-free t))
- `(lambda () ,@body))
-
-(defmacro λi (&rest body)
- "Convenience macro to create interactive lambda functions that take
-no arguments with much shorter and concise syntax. Calling ‘λi’ with
-BODY is equivalent to calling ‘lambda’ with an empty argument list and
-BODY directly after ‘(interactive)’."
- (declare (pure t) (side-effect-free t))
- `(lambda () (interactive) ,@body))
-
-(defun x-mode-to-hook (mode)
+;;; init.el --- Main Emacs configuration file -*- lexical-binding: t; -*-
+
+;;; Preamble
+
+;; To inhibit this message you must do this in init.el (not
+;; early-init.el!), you must use ‘setq’ (not ‘setopt’!), and you must
+;; write your login name as a string (you shan’t use ‘user-login-name’!).
+;; Lord knows why this needs to be so complicated…
+;;
+;; The ‘eval’ is required in the case that this file is byte-compiled.
+(if mm-darwin-p
+ (eval '(setq inhibit-startup-echo-area-message "thomasvoss"))
+ (eval '(setq inhibit-startup-echo-area-message "thomas")))
+
+;; Add all my custom lisp code into the load path
+(dolist (directory
+ (list mm-config-directory
+ (expand-file-name "modules" mm-config-directory)
+ (expand-file-name "site-lisp" mm-config-directory)))
+ (add-to-list 'load-path directory))
+
+
+;;; Convenience Macros and -Functions
+
+(defun mm-mode-to-hook (mode)
"Get the hook corresponding to MODE."
- (declare (pure t) (side-effect-free t))
+ (declare (ftype (function (symbol) symbol))
+ (pure t) (side-effect-free t))
(intern (concat (symbol-name mode) "-hook")))
-(defun x-mode-to-ts-mode (mode)
- "Get the tree-sitter mode corresponding to MODE."
- (declare (pure t) (side-effect-free t))
+(defun mm-mode-to-ts-mode (mode)
+ "Get the Tree-Sitter mode corresponding to MODE."
+ (declare (ftype (function (symbol) symbol))
+ (pure t) (side-effect-free t))
(intern (concat
(string-remove-suffix "-mode" (symbol-name mode))
"-ts-mode")))
-(defun x-ts-mode-to-mode (ts-mode)
- "Get the non-tree-sitter mode corresponding to TS-MODE."
- (declare (pure t) (side-effect-free t))
+(defun mm-ts-mode-to-mode (ts-mode)
+ "Get the non-Tree-Sitter mode corresponding to TS-MODE."
+ (declare (ftype (function (symbol) symbol))
+ (pure t) (side-effect-free t))
(intern (concat
(string-remove-suffix "-ts-mode" (symbol-name ts-mode))
"-mode")))
-(defun x-string-split (separators string)
- "Wrapper around ‘string-split' that puts SEPARATORS first. This makes
-it convenient to use in ‘thread-last’."
- (declare (pure t) (side-effect-free t))
+(defsubst mm-string-split (separators string)
+ "Split STRING on SEPARATORS.
+Wrapper around `string-split' that puts separators first. This makes it
+convenient to use in `thread-last'."
+ (declare (ftype (function (string string) (list string)))
+ (pure t) (side-effect-free t))
(string-split string separators))
-(defun x-do-and-center (function &rest arguments)
+(defun mm-as-number (string-or-number)
+ "Ensure STRING-OR-NUMBER is a number.
+If given a number return STRING-OR-NUMBER as-is, otherwise convert it to
+a number and then return it.
+
+This function is meant to be used in conjuction with `read-string' and
+`format-prompt'."
+ (declare (ftype (function (or string number) number))
+ (pure t) (side-effect-free t))
+ (if (stringp string-or-number)
+ (string-to-number string-or-number)
+ string-or-number))
+
+(defun mm-do-and-center (function &rest arguments)
"Call FUNCTION with ARGUMENTS and then center the screen."
(apply function arguments)
(recenter))
-(defun x-no-tab-indentation (function &rest arguments)
- "Call FUNCTION with ARGUMENTS in an environment in which
-`indent-tabs-mode' is nil."
- (let (indent-tabs-mode)
- (apply function arguments)))
-
-(defmacro x-comment (&rest _body)
+(defmacro mm-comment (&rest _body)
"Comment out BODY. A cleaner alternative to line-commenting a region."
(declare (indent 0))
nil)
-(defmacro x-with-suppressed-output (&rest body)
- "Execute BODY while suppressing output to the echo area or the
-*Messages* buffer."
+(defmacro mm-with-suppressed-output (&rest body)
+ "Execute BODY while suppressing output.
+Execute BODY as given with all output to the echo area or the *Messages*
+buffer suppressed."
(declare (indent 0))
`(let ((inhibit-message t)
(message-log-max nil))
- ,@body))
-
- ;;; Rational Defaults
-(prefer-coding-system 'utf-8)
-(savehist-mode)
-(global-hl-line-mode)
-(setq disabled-command-function nil)
-(x-set custom-safe-themes t)
-
-(dolist (mode #'(blink-cursor-mode show-paren-mode tooltip-mode))
- (apply mode '(-1)))
-
-(x-set
- large-file-warning-threshold nil
- vc-follow-symlinks t
- ad-redefinition-action 'accept)
-
-(x-set
- mouse-wheel-scroll-amount '(1 ((shift) . 1))
- mouse-wheel-progressive-speed nil
- mouse-wheel-follow-mouse t
- scroll-step 1)
-(pixel-scroll-precision-mode)
-
-(x-set show-paren-delay 0)
-(dolist (hook '(conf-mode-hook prog-mode-hook helpful-mode-hook))
- (add-hook hook #'show-paren-local-mode))
-
-(x-set read-extended-command-predicate
- #'command-completion-default-include-p)
-
-(setq-default display-line-numbers 'relative)
-(line-number-mode)
-(column-number-mode)
-
-;; Backup settings
-(x-set
- delete-old-versions t
- version-control t
- kept-new-versions 2
- kept-old-versions 2)
-
-(setq-default fill-column 73)
-(add-hook 'text-mode-hook #'auto-fill-mode)
-
-(require 'autorevert)
-(x-set global-auto-revert-non-file-buffers t)
-(global-auto-revert-mode)
-
-(x-set custom-file (expand-file-name
- (format "emacs-custom-%s.el" (user-uid))
- temporary-file-directory))
-(load custom-file :noerror)
-
-(define-key global-map [remap backward-delete-char-untabify]
- #'backward-delete-char)
-
- ;;; Documentation Improvements
-(use-package helpful
- :bind (([remap describe-command] . helpful-command)
- ([remap describe-function] . helpful-callable)
- ([remap describe-key] . helpful-key)
- ([remap describe-symbol] . helpful-symbol)
- ([remap describe-variable] . helpful-variable)
- (("C-h C-p" . helpful-at-point))))
-
- ;;; Vim Emulation
-(use-package evil
- :init
- (setq ;; All of the following must be set before loading ‘evil-mode’
- evil-want-Y-yank-to-eol t
- evil-v$-excludes-newline t
- evil-split-window-below t
- evil-vsplit-window-right t
- evil-want-fine-undo t
- evil-undo-system #'undo-redo
- evil-flash-delay 1
- evil-want-keybinding nil)
- (evil-mode)
- (global-visual-line-mode)
- :config
- (evil-define-operator x-evil-align-regexp (start end regexp repeat)
- "Evil operator for ‘align-regexp’."
- :move-point nil
- :restore-point t
- (interactive (let ((range (evil-operator-range)))
- (list (car range)
- (cadr range)
- (concat "\\(\\s-*\\)"
- (read-string
- (format-prompt "Align regexp" nil)))
- (y-or-n-p "Repeat? "))))
- (align-regexp start end regexp 1 1 repeat))
-
- (evil-define-operator x-evil-comment-or-uncomment-region (start end)
- "Evil operator for ‘comment-or-uncomment-region’."
- :move-point nil
- :restore-point t
- (comment-or-uncomment-region start end))
-
- (evil-define-operator x-evil-sort-lines (start end)
- "Evil operator for ‘sort-lines’."
- :move-point nil
- :restore-point t
- (sort-lines nil start end)))
-
-(defmacro x-evil-define-and-bind-quoted-text-object (name key start-regexp end-regexp)
- (let ((inner-name (make-symbol (concat "evil-inner-" name)))
- (outer-name (make-symbol (concat "evil-a-" name))))
- `(progn
- (evil-define-text-object ,inner-name (count &optional beg end type)
- (evil-select-paren ,start-regexp ,end-regexp beg end type count nil))
- (evil-define-text-object ,outer-name (count &optional beg end type)
- (evil-select-paren ,start-regexp ,end-regexp beg end type count :inclusive))
- (define-key evil-inner-text-objects-map ,key #',inner-name)
- (define-key evil-outer-text-objects-map ,key #',outer-name))))
-
-(defun x-evil-surround-function ()
- "Read a function name from the minibuffer and index the list with the
-selection. This is nearly identical to `evil-surround-function' except
-it provides a useful prompt, and is language-aware."
- (let ((list-name
- (or (evil-surround-read-from-minibuffer "Function name: ") "")))
- (if (derived-mode-p #'(lisp-mode lisp-data-mode emacs-lisp-mode))
- (cons (format "(%s " list-name) ")")
- (cons (format "%s(" (or list-name "")) ")"))))
-
-(defun x-evil-surround-mode-if-evil-mode ()
- "Enable `global-evil-surround-mode' if `evil-mode' is active, and
-disable it otherwise."
- (global-evil-surround-mode (unless (evil-mode) -1)))
-
-(defun x-evil-surround-list ()
- "Read a list name from the minibuffer and index the list with the
-selection."
- (let ((list-name (evil-surround-read-from-minibuffer "List name: ")))
- (cons (format "%s[" (or list-name "")) "]")))
-
-(use-package evil-surround
- :after evil
- :hook (evil-mode . x-evil-surround-mode-if-evil-mode)
- :init
- (x-evil-surround-mode-if-evil-mode)
- :config
- (x-evil-define-and-bind-quoted-text-object "single-quote-open" "‘" "‘" "’")
- (x-evil-define-and-bind-quoted-text-object "single-quote-close" "’" "‘" "’")
- (x-evil-define-and-bind-quoted-text-object "double-quote-open" "“" "“" "”")
- (x-evil-define-and-bind-quoted-text-object "double-quote-open" "“" "“" "”")
-
- (setq-default
- evil-surround-pairs-alist
- (append
- '((?‘ . ("‘ " . " ’"))
- (?’ . ("‘" . "’"))
- (?“ . ("“ " . " ”"))
- (?“ . ("“" . "”"))
- (?f . x-evil-surround-function)
- (?l . x-evil-surround-list))
- evil-surround-pairs-alist)))
-
-(use-package evil-collection
- :after evil
- :init
- (evil-collection-init))
+ ,@body))
- ;;; Force Spaces For Alighment
-(dolist (f #'(align-regexp c-backslash-region))
- (advice-add f :around #'x-no-tab-indentation))
+
+;;; Silent Native Compilation
- ;;; Minibuffer Improvements
-(use-package vertico
- :custom (vertico-cycle t)
- :init (vertico-mode))
+(when (native-comp-available-p)
+ (setopt
+ native-comp-async-report-warnings-errors nil
+ native-compile-prune-cache t))
+
+
+;;; Package Management
+
+(setopt
+ package-vc-register-as-project nil
+ package-gnupghome-dir (getenv "GNUPGHOME")
+ package-user-dir (expand-file-name "pkg" mm-data-directory)
+ package-archives (cl-loop with proto = (if (gnutls-available-p) "https" "http")
+ for (name . url) in
+ '(("gnu" . "elpa.gnu.org/packages/")
+ ("melpa" . "melpa.org/packages/")
+ ("nongnu" . "elpa.nongnu.org/nongnu/"))
+ collect (cons name (concat proto "://" url)))
+ package-archive-priorities '(("gnu" . 3)
+ ("nongnu" . 2)
+ ("melpa" . 1)))
+(package-initialize)
+(setopt use-package-always-defer t)
+
+
+;;; Generic Emacs Configuration
+
+(defvar mm-initial-scratch-message
+ (format
+ ";; This is `%s'. Use `%s' to evaluate and print results.\n\n"
+ initial-major-mode
+ (propertize
+ (substitute-command-keys
+ "\\<lisp-interaction-mode-map>\\[eval-print-last-sexp]")))
+ "The initial message to display in the scratch buffer.")
+
+(use-package emacs
+ :demand t
+ :custom
+ (ad-redefinition-action 'accept)
+ (create-lockfiles nil)
+ (custom-file (expand-file-name "custom.el" mm-config-directory))
+ (custom-safe-themes t)
+ (delete-pair-blink-delay 0)
+ (disabled-command-function nil)
+ (duplicate-line-final-position -1)
+ (duplicate-region-final-position -1)
+ (echo-keystrokes-help nil)
+ (help-window-select t)
+ (initial-buffer-choice t)
+ (initial-scratch-message mm-initial-scratch-message)
+ (kill-do-not-save-duplicates t)
+ (large-file-warning-threshold nil)
+ (make-backup-files nil)
+ (next-error-recenter '(4)) ; ‘center of window’
+ (read-extended-command-predicate #'command-completion-default-include-p)
+ (remote-file-name-inhibit-auto-save t)
+ (remote-file-name-inhibit-delete-by-moving-to-trash t)
+ (save-interprogram-paste-before-kill t)
+ (scroll-conservatively 101) ; (info "(Emacs)Auto Scrolling")
+ (scroll-error-top-bottom t)
+ (show-trailing-whitespace t)
+ (user-full-name "Thomas Voss")
+ (user-mail-address "mail@thomasvoss.com")
+ (vc-follow-symlinks t)
+ (vc-handled-backends '(Git))
+ :config
+ (load custom-file :noerror)
+ (setq-default
+ fill-column 80
+ truncate-partial-width-windows nil)
+ (dolist (mode '(text-mode emacs-lisp-mode lisp-mode))
+ (add-hook (mm-mode-to-hook mode)
+ (defun mm-set-fill-column ()
+ (setq-local fill-column 73))))
+ (add-hook 'text-mode-hook #'auto-fill-mode)
+ (prefer-coding-system 'utf-8)
+
+ ;; Proper Mac support
+ (when mm-darwin-p
+ (setopt
+ mac-option-key-is-meta nil
+ mac-command-key-is-meta t
+ mac-command-modifier 'meta
+ mac-option-modifier 'none))
+
+ ;; Disabled modes
+ (blink-cursor-mode -1)
+ (line-number-mode -1)
+ (tooltip-mode -1))
+
+
+;;; Instantly highlight matching parens
+
+(use-package paren
+ :custom
+ (show-paren-delay 0))
-(use-package marginalia
- :after vertico
- :init (marginalia-mode))
+
+;;; Display Line Numbers
-(use-package orderless
+(use-package display-line-numbers
+ :hook prog-mode
:custom
- (completion-styles '(orderless basic))
- (orderless-matching-styles '(orderless-prefixes))
- (completion-category-overrides '((file (styles basic partial-completion)))))
+ (display-line-numbers-type 'relative)
+ (display-line-numbers-width-start 99)
+ (display-line-numbers-grow-only t))
+
+
+;;; Auto Revert Buffers
- ;;; Completions
-(use-package corfu
- :hook ((prog-mode . corfu-mode))
+(use-package autorevert
:custom
- (corfu-auto t)
- (corfu-cycle t)
- (corfu-auto-prefix 1)
- (corfu-auto-delay 0))
-
- ;;; Increment- and Decrement Numbers
-(use-package increment
- :ensure nil
- :commands (increment-number-at-point
- decrement-number-at-point)
+ (global-auto-revert-non-file-buffers t)
:init
- (evil-define-key '(normal visual) 'global
- (kbd "<leader> n i") #'increment-number-at-point
- (kbd "<leader> n d") #'decrement-number-at-point))
-
- ;;; Indentation Settings
-(setq-default
- tab-width 4
- indent-tabs-mode t)
-(x-set evil-shift-width (default-value 'tab-width))
-
-(defvar x-indentation-settings
- '((c-mode :extra-vars (c-basic-offset))
- (c-ts-mode :extra-vars (c-ts-mode-indent-offset))
- (css-mode :extra-vars (css-indent-offset))
- (emacs-lisp-mode :width 8 :spaces t) ;; GNU code uses 8-column tabs
- (go-ts-mode :extra-vars (go-ts-mode-indent-offset))
- (graphviz-dot-mode :extra-vars (graphviz-dot-indent-width))
- (gsp-ts-mode :width 2 :extra-vars (gsp-ts-indent-rules))
- (helpful-mode :width 8)
- (lisp-data-mode :spaces t)
- (lisp-mode :spaces t)
- (org-mode :spaces t)
- (python-mode :extra-vars (python-indent-offset))
- (sgml-mode :width 2 :extra-vars (sgml-basic-offset))
- (sh-mode :extra-vars (sh-basic-offset))
- (vimscript-ts-mode :extra-vars (vimscript-ts-mode-indent-level)))
- "A list of per-mode indentation settings. Each list contains a
-major-mode and the 3 optional keyword arguments of :spaces, :width, and
-:extra-vars. When setting the settings for a given major-mode, the
-settings will also be applied for that mode’s tree-sitter variant.
-
-If :spaces is non-nil, then indentation will be performed with spaces
-instead of tabs characters.
-
-If :width is non-nil, then it will override the modes given tab-width.
-
-If :extra-vars is non-nill, then it shall be a list of additional
-mode-specific variables that need to be assigned the desired
-indentation-width.")
-
-(defun x-set-indentation-settings ()
- "Apply the indentation settings specified by ‘x-indentation-settings’."
- (interactive)
- (dolist (plist x-indentation-settings)
- (let* ((mode (car plist))
- (args (cdr plist))
- (width (or (plist-get args :width)
- (default-value 'tab-width)))
- (spaces (or (plist-get args :spaces)
- (not (default-value 'indent-tabs-mode))))
- (extra (plist-get args :extra-vars))
- (callback
- (λ (indent-tabs-mode (when spaces -1))
- (setq-local tab-width width
- evil-shift-width width)
- (dolist (var extra)
- (set var width)))))
- (add-hook (x-mode-to-hook mode) callback 95)
- (unless (string-prefix-p "-ts-mode" (symbol-name mode))
- (add-hook (x-mode-to-hook (x-mode-to-ts-mode mode)) callback 95)))))
-
-(defun x-set-tabsize ()
- "Set the tabsize for the current buffer. If the current buffers major
-mode requires settings additional variables, those should be listed in
-`x-indentation-settings'."
- (interactive)
- (let* ((major-mode-2
- (if (string-match-p "-ts-mode\\'" (symbol-name major-mode))
- (x-ts-mode-to-mode major-mode)
- (x-mode-to-ts-mode major-mode)))
- (prompt-default (number-to-string (default-value 'tab-width)))
- (tabsize (string-to-number
- (read-string
- (format-prompt "Tabsize" prompt-default)
- nil nil prompt-default))))
- (setq-local
- tab-width tabsize
- evil-shift-width tabsize)
- (dolist (plist x-indentation-settings)
- (let ((mode (car plist))
- (extra (plist-get (cdr plist) :extra-vars)))
- (when (or (eq mode major-mode)
- (eq mode major-mode-2))
- (mapc (lambda (var)
- (set (make-local-variable var) tabsize))
- extra))))))
-
-(x-set-indentation-settings)
-
- ;;; Git Integration
-(use-package magit
- :custom
- (magit-display-buffer-function
- #'magit-display-buffer-same-window-except-diff-v1)
- :config
- (transient-define-suffix x-magit-push-current-to-all-remotes (args)
- "Push the current branch to all remotes."
- :if #'magit-get-current-branch
- (interactive (list (magit-push-arguments)))
- (run-hooks 'magit-credential-hook)
- (let ((branch (magit-get-current-branch)))
- (dolist (remote (magit-list-remotes))
- (magit-run-git-async
- "push" "-v" args remote
- (format "refs/heads/%s:refs/heads/%s" branch branch)))))
- (transient-append-suffix 'magit-push '(1 -1)
- '("a" "all branches" x-magit-push-current-to-all-remotes)))
-
-(use-package magit-todos
- :after magit
- :init (magit-todos-mode)
- :custom
- (magit-todos-exclude-globs '(".git/" "vendor/")))
-
- ;;; Tree-Sitter
-(when (treesit-available-p)
- (x-set treesit-font-lock-level 4)
-
- (setq treesit-language-source-alist
- '((cpp "https://github.com/tree-sitter/tree-sitter-cpp")
- (css "https://github.com/tree-sitter/tree-sitter-css")
- (elisp "https://github.com/Wilfred/tree-sitter-elisp")
- (go "https://github.com/tree-sitter/tree-sitter-go")
- (gomod "https://github.com/camdencheek/tree-sitter-go-mod")
- (gsp "git://git.thomasvoss.com/tree-sitter-gsp.git")
- (html "https://github.com/tree-sitter/tree-sitter-html")
- (javascript "https://github.com/tree-sitter/tree-sitter-javascript")))
-
- (defun x-treesit-source-sync ()
- "Install all the tree-sitter grammars in
-‘treesit-language-source-alist’. This function does not assert whether
-or not the grammar is already installed, making it useful for updating
-existing grammars."
- (interactive)
- (async-start
- `(lambda ()
- ,(async-inject-variables "\\`treesit-language-source-alist\\'")
- (dolist (spec treesit-language-source-alist)
- (treesit-install-language-grammar (car spec))))
- (lambda (message)
- (message "Done syncing Tree-Sitter grammars"))))
-
- (thread-last
- (mapcar #'car treesit-language-source-alist)
- (seq-remove #'treesit-language-available-p)
- (mapc #'treesit-install-language-grammar))
-
- (dolist (pair '(("\\.[ch]\\'" . c-ts-mode)
- ("\\.css\\'" . css-ts-mode)
- ("\\.js\\'" . js-ts-mode)))
- (add-to-list 'auto-mode-alist pair)))
-
- ;;; Language Server Protocol
-(use-package eglot
- :hook ((c-mode . eglot-ensure)
- (c++-mode . eglot-ensure)
- (c-ts-mode . eglot-ensure)
- (c++-ts-mode . eglot-ensure)
- (go-ts-mode . eglot-ensure))
+ (add-hook
+ 'after-change-major-mode-hook
+ (defun mm-enable-autorevert ()
+ (unless (derived-mode-p 'Buffer-menu-mode)
+ (auto-revert-mode)))))
+
+
+;;; Smoother Scrolling
+
+(use-package pixel-scroll
:init
- (fset #'jsonrpc--log-event #'ignore)
- :custom
- (eglot-events-buffer 0)
- (eglot-extend-to-xref t)
+ (pixel-scroll-precision-mode)
:config
- (add-hook 'eglot-managed-mode-hook (λ (eglot-inlay-hints-mode -1)))
- (dolist (feature '(eldoc flymake))
- (add-to-list 'eglot-stay-out-of feature))
- (with-eval-after-load 'eglot
- (add-to-list 'eglot-server-programs
- '((c-mode c-ts-mode c++-mode c++-ts-mode)
- . ("clangd" "--header-insertion=never")))))
-
-(use-package eglot-booster
- :after eglot
- :config (eglot-booster-mode))
-
- ;;; Snippet Support
-(use-package tempel
- :pin gnu
- :custom
- (tempel-trigger-prefix ",")
- :init
- (dolist (mode '(conf-mode prog-mode text-mode))
- (add-hook
- (x-mode-to-hook mode)
- (λ (add-hook 'completion-at-point-functions
- #'tempel-complete -10 :local))))
- (x-set tempel-path (expand-file-name "templates" x-config-directory))
- (push (cons tempel-path #'lisp-data-mode) auto-mode-alist))
-
-(use-package eglot-tempel
- :init
- (with-eval-after-load 'eglot
- (add-hook 'eglot-managed-mode-hook
- (λ (unless (default-value eglot-tempel-mode)
- (eglot-tempel-mode))))))
-
- ;;; Automatically Create Directories
-(defun x-auto-create-directories (original-function filename &rest arguments)
- "Automatically create and delete parent directories of files. This
-is an ‘:override’ advice for ‘find-file’ and friends. It
-automatically creates the parent directory (or directories) of the
-file being visited, if necessary. It also sets a buffer-local
-variable so that the user will be prompted to delete the newly created
-directories if they kill the buffer without saving it."
+ ;; Make it easier to use custom scroll functions
+ (dolist (binding '("<next>" "<prior>"))
+ (keymap-unset pixel-scroll-precision-mode-map binding :remove)))
+
+
+;;; Automatically Create- and Delete Directories
+
+(defun mm-auto-create-directories (function filename &rest arguments)
+ "Automatically create and delete parent directories of files.
+This is an `:override' advice for `find-file' and friends. It
+automatically creates the parent directories of the file being visited
+if necessary. It also sets a buffer-local variable so that the user
+will be prompted to delete the newly created directories if they kill
+the buffer without saving it."
(let (dirs-to-delete)
(let* ((dir-to-create (file-name-directory filename))
(current-dir dir-to-create))
- ;; We want to go up each directory component and add them to
- ;; ‘dirs-to-delete’ individually.
+ ;; Add each directory component to ‘dirs-to-delete’
(while (not (file-exists-p current-dir))
(push current-dir dirs-to-delete)
(setq current-dir (file-name-directory
(directory-file-name current-dir))))
-
(unless (file-exists-p dir-to-create)
(make-directory dir-to-create :parents)))
-
- ;; Use ‘prog1’ so that we maintain the original return value
- (prog1 (apply original-function filename arguments)
+ (prog1
+ (apply function filename arguments)
(when dirs-to-delete
- (setq-local x-dirs-to-delete (reverse dirs-to-delete))
-
- ;; When we kill the buffer we want to ask if we should delete parent
- ;; directories *unless* the buffer was saved, in which case we don’t
- ;; want to do anything.
- (add-hook 'kill-buffer-hook #'x-delete-directories-if-appropriate
+ (setq-local mm-find-file--dirs-to-delete (reverse dirs-to-delete))
+ (add-hook 'kill-buffer-hook #'mm-find-file--maybe-delete-directories
:depth :local)
- (add-hook 'after-save-hook #'x-remove-auto-directory-hooks
+ (add-hook 'after-save-hook #'mm-find-file--remove-hooks
:depth :local)))))
-(dolist (command #'(find-file find-alternate-file write-file))
- (advice-add command :around #'x-auto-create-directories))
-
-(defun x-delete-directories-if-appropriate ()
- "Delete parent directories if appropriate. This is a function for
-‘kill-buffer-hook’. If ‘x-auto-create-directories’ created the
-directory containing the file for the current buffer automatically,
-then offer to delete it. Otherwise, do nothing. Also clean up
-related hooks."
+(defun mm-find-file--maybe-delete-directories ()
(unless (file-exists-p buffer-file-name)
- (dolist (dir-to-delete x-dirs-to-delete)
- (when (and (stringp dir-to-delete)
- (file-exists-p dir-to-delete)
+ (dolist (directory mm-find-file--dirs-to-delete)
+ (when (and (stringp directory)
+ (file-exists-p directory)
(thread-last
- (directory-file-name dir-to-delete)
- (format-prompt "Also delete directory `%s'?" nil)
- (string-remove-suffix ": ")
+ (directory-file-name directory)
+ (format "Also delete directory `%s'?")
+ (substitute-quotes)
(y-or-n-p)))
- (delete-directory dir-to-delete)))))
-
-(defun x-remove-auto-directory-hooks ()
- "Clean up directory-deletion hooks, if necessary."
- (remove-hook 'kill-buffer-hook #'x-delete-directories-if-appropriate :local)
- (remove-hook 'after-save-hook #'x-remove-auto-directory-hooks :local))
-
- ;;; Colorize Compilation Buffer
-(require 'ansi-color)
-(defun x-colorize-buffer ()
- "Parse ANSI escape sequences in the current buffer."
- (let ((inhibit-read-only t))
- (ansi-color-apply-on-region (point-min) (point-max))))
-(add-hook 'compilation-filter-hook #'x-colorize-buffer)
-
- ;;; Autokill Complation Buffer On Success
-(defun x--compilation-count-number-of-windows (&rest _arguments)
- (setq x--compilation-number-of-windows (length (window-list))))
-(advice-add #'compile :before #'x--compilation-count-number-of-windows)
-
-(defun x--kill-compilation-buffer-on-success (buffer string)
- (when (string= string "finished\n")
- (with-current-buffer buffer
- (when (> (length (window-list))
- x--compilation-number-of-windows)
- (delete-window (get-buffer-window)))
- (kill-buffer))))
-(add-hook 'compilation-finish-functions #'x--kill-compilation-buffer-on-success)
-
- ;;; User Interface Themeing
-(load-theme 'mango :no-confirm)
-
-(defvar x-alpha-background 90
- "The opacity of a graphical Emacs frame, ranging from 0–100. A value
-of 0 is fully transparent while a value of 100 is fully opaque.")
-
-(defun x-set-alpha-background (value)
- "Set the current frames’ background opacity to VALUE."
- (interactive "NOpacity [0–100]: ")
- (set-frame-parameter nil 'alpha-background value))
-(add-to-list
- 'default-frame-alist (cons 'alpha-background x-alpha-background))
-
-(defvar x-monospace-font '("Iosevka Smooth" :weight regular :height 162)
- "The default monospace font to use. This is a list containing a
-font name, font weight, and font height in that order.")
-
-(defvar x-proportional-font '("Ysabeau" :weight light :height 180)
- "The default proportional font to use. This is a list containing a
-font name, font weight, and font height in that order.")
-
-(defun x-set-fonts ()
- "Set the fonts specified by ‘x-monospace-font’ and
-‘x-proportional-font’."
- (interactive)
- (let* ((mono-family (car x-monospace-font))
- (mono-props (cdr x-monospace-font))
- (prop-family (car x-proportional-font))
- (prop-props (cdr x-proportional-font))
- (mono-weight (plist-get mono-props :weight))
- (mono-height (plist-get mono-props :height))
- (prop-weight (plist-get prop-props :weight))
- (prop-height (plist-get prop-props :height)))
- (set-face-attribute 'default nil
- :font mono-family
- :weight mono-weight
- :height mono-height)
- (set-face-attribute 'fixed-pitch nil
- :font mono-family
- :weight mono-weight
- :height mono-height)
- (set-face-attribute 'variable-pitch nil
- :font prop-family
- :weight prop-weight
- :height prop-height)))
-
-(if (daemonp)
- (add-hook 'after-make-frame-functions
- (lambda (_frame)
- (x-set-fonts)))
- (x-set-fonts))
-
-;; Setup ligatures
-(use-package ligature
- :defer nil
- :if
- (and (seq-contains-p (split-string system-configuration-features) "HARFBUZZ")
- (display-graphic-p))
- :init
- (defvar x-ligatures-alist
- '(((c-mode c-ts-mode go-ts-mode) . ("<=" ">=" "==" "!=" "*=" "__"))
- (c-mode c-ts-mode . ("->"))
- (go-ts-mode . (":=" "<-"))
- ((mhtml-mode html-mode html-ts-mode) . ("<!--" "-->" "/>")))
- "Ligatures to enable in specific modes.
-Elements of this alist are of the form:
-
- (SPEC . LIGATURES)
-
-where LIGATURES is a list of ligatures to enable for the set of modes
-described by SPEC.
-
-SPEC can be either a symbol, or a list of symbols. These symbols should
-correspond to modes for which the associated ligatures should be enabled.
-
-A mode may also be specified in multiple entries. To configure
-`go-ts-mode' to have it’s set of ligatures be a super-set of the
-ligatures for `c-ts-mode', the following two entries could be added:
-
- '((c-ts-mode go-ts-mode) . (\">=\" \"<=\" \"!=\" \"==\"))
- '(go-ts-mode . (\":=\"))")
-
- (defun x-set-ligatures ()
- (interactive)
- (setq ligature-composition-table nil)
- (dolist (pair x-ligatures-alist)
- (ligature-set-ligatures (car pair) (cdr pair))))
- :config
- (x-set-ligatures)
- (global-ligature-mode))
-
- ;;; Projects
-(defun x-project-magit-status ()
- "Open a Git status buffer for the currently selected project. This is
-intended to be used in `project-switch-commands'."
- (interactive)
- (thread-last
- (project-current t)
- (project-root)
- (magit-status)))
-
-(use-package project
- :ensure nil
- :defer nil
- :config
- (x-with-suppressed-output
- (mapc #'project-remember-projects-under
- (directory-files (getenv "REPODIR") :full "\\`[^.]")))
- (x-set project-switch-commands
- '((project-dired "Dired")
- (project-find-file "Find File")
- (project-find-regexp "Find Regexp")
- (x-project-magit-status "Git Status" ?s))))
-
- ;;; C-Style
-(setq-default
- c-auto-newline t
- c-hungry-delete-key t)
-
-(defun x-c-defun-open-safe (_syntax _position)
- (if (c-cpp-define-name)
- '(after)
- '(before after)))
-
-(defun x-c-semi&comma-after-return ()
- "‘c-mode’ criteria to avoid automatic newline insertion after entering
-a semicolon following a return statement."
- (catch 'return
- (let ((end-position (point)))
- (save-excursion
- (goto-char (line-beginning-position))
- (save-match-data
- (while (re-search-forward "\\<return\\>" end-position :noerror)
- (when (eq (get-text-property (1- (point)) 'face)
- 'font-lock-keyword-face)
- (throw 'return 'stop))))))))
-
-(c-add-style
- "mango"
- '((indent-tabs-mode . t)
- (c-backslash-column . 80)
- (c-backslash-max-column . 80)
- (c-basic-offset . 4)
- (c-block-comment-prefix . "")
- (c-comment-only-line-offset . 0)
- (c-label-minimum-indentation . 0)
- (c-hanging-semi&comma-criteria . (x-c-semi&comma-after-return
- c-semi&comma-inside-parenlist
- c-semi&comma-no-newlines-before-nonblanks
- c-semi&comma-no-newlines-for-oneline-inliners))
- (c-cleanup-list . (brace-else-brace
- brace-elseif-brace
- brace-catch-brace
- comment-close-slash
- scope-operator))
- (c-offsets-alist . ((label . [0])
- (arglist-intro . +)
- (arglist-cont . 0)
- (arglist-cont-nonempty . +)
- (arglist-close . 0)))
- (c-hanging-braces-alist . ((defun-open . x-c-defun-open-safe)
- (defun-close before)
- (class-open after)
- (class-close before)
- (block-open after)
- (block-close . c-snug-do-while)
- (brace-list-open after)
- (brace-list-close before)
- (extern-lang-open after)
- (extern-lang-close before)
- (substatement-open after)
- (statement-case-open after)))
- (c-hanging-colons-alist . ((case-label after)
- (label after)))))
-(customize-set-variable 'c-default-style "mango")
-
- ;;; Additional Mode Support
-(eval-and-compile
- (defun x-gsp-ts-mode-load-path ()
- (expand-file-name "Mango0x45/gsp-ts-mode" (getenv "REPODIR"))))
-(use-package gsp-ts-mode
- :if (and (treesit-available-p)
- (getenv "REPODIR"))
- :load-path (lambda () (list (x-gsp-ts-mode-load-path)))
- :mode ("\\.gsp\\'" . gsp-ts-mode))
-
-(use-package vimscript-ts-mode)
-(push '("\\.go\\'" . go-ts-mode) auto-mode-alist)
-
- ;;; Popup Windows
-(require 'popup)
-(popup-define full-calc)
-(use-package calc
- :ensure nil
- :config
- (defun x-calc-quit ()
- (interactive)
- (if (popupp)
- (delete-frame)
- (calc-quit :non-fatal)))
- (evil-define-key 'normal 'calc-mode-map "q" #'x-calc-quit))
-
- ;;; Keybindings
-(defmacro x-define-bindings (&rest body)
- (declare (indent 0))
- (let (head result keymap)
- (while body
- (setq
- head (car body)
- body (if (eq head :map)
- (progn (setq keymap (cadr body)) (cddr body))
- (push (list #'keymap-set
- keymap
- (car head)
- (let ((function (cadr head)))
- (if (symbolp function)
- (macroexp-quote function)
- function)))
- result)
- (cdr body))))
- (macroexp-progn result)))
-
-(defmacro x-define-evil-bindings (&rest body)
- (declare (indent 0))
- (let (mode-spec head result keymap prefix)
- (while body
- (setq
- head (car body)
- body (cond ((eq head :prefix)
- (setq prefix (cadr body))
- (cddr body))
- ((eq head :map)
- (setq keymap (cadr body))
- (cddr body))
- ((and (symbolp head) (string-prefix-p ":" (symbol-name head)))
- (setq mode-spec
- (thread-last
- (symbol-name head)
- (string-remove-prefix ":")
- (x-string-split "&")
- (mapcar #'intern)))
- (cdr body))
- (t
- (push (list #'evil-define-key
- (macroexp-quote mode-spec)
- (or keymap (macroexp-quote 'global))
- (kbd (concat "<leader>" prefix (car head)))
- (macroexp-quote (cadr head)))
- result)
- (cdr body)))))
- (macroexp-progn result)))
-
-;; <leader> and <localleader>
-(evil-set-leader nil (kbd "SPC"))
-(evil-set-leader nil (kbd ",") :localleader)
-
-;; Evil bindings that aren’t namespaced under ‘<leader>’
-(evil-define-key '(normal visual) 'global
- "gc" #'x-evil-comment-or-uncomment-region
- "V" #'evil-visual-block)
-
-(let ((modes '(normal insert visual operator motion)))
- (evil-define-key modes 'global
- (kbd "C-u") (λi (x-do-and-center #'evil-scroll-up 0))
- (kbd "C-d") (λi (x-do-and-center #'evil-scroll-down 0))
- (kbd "C-v") #'evil-visual-line))
-
-(defun x-minibuffer-backward-kill (arg)
- "When minibuffer completing a filename, delete up to the parent folder,
-otherwise delete a word."
- (interactive "p")
- (if minibuffer-completing-file-name
- (if (string-match-p "/." (minibuffer-contents))
- (zap-up-to-char (- arg) ?/)
- (delete-minibuffer-contents))
- (backward-kill-word arg)))
-(keymap-set minibuffer-local-map "C-h" #'x-minibuffer-backward-kill)
-
-(with-eval-after-load 'vertico
- (x-define-bindings
- :map vertico-map
- ("C-j" vertico-next)
- ("C-k" vertico-previous)
- ("C-l" vertico-insert)))
-
-(defun x-company-select-candidate (pred)
- "Select either the next or previous candidate in the candidate list based on
-the comparison of the ‘company-pseudo-tooltip-overlay’ height and 0 using PRED."
- (let ((ov company-pseudo-tooltip-overlay))
- (if (and ov (apply pred (list (overlay-get ov 'company-height) 0)))
- (company-select-previous)
- (company-select-next))))
-
-(with-eval-after-load 'company
- (x-define-bindings
- :map company-active-map
- ("C-j" (λi (x-company-select-candidate #'<)))
- ("C-k" (λi (x-company-select-candidate #'>)))))
-
-(with-eval-after-load 'tempel
- (x-define-bindings
- :map tempel-map
- ("C-l" tempel-next)
- ("C-h" tempel-previous)))
-
-(with-eval-after-load 'yasnippet
- (x-define-bindings
- :map yas-minor-mode-map
- ("C-l" (λi (when (yas-current-field) (yas-next-field))))
- ("C-h" (λi (when (yas-current-field) (yas-prev-field))))))
-
-(defun x-tetris-rotate-mirror ()
- (interactive)
- (tetris-rotate-next)
- (tetris-rotate-next))
-
-(with-eval-after-load 'tetris
- (x-define-bindings
- :map tetris-mode-map
- ("a" tetris-move-left)
- ("d" tetris-move-right)
- ("k" tetris-rotate-next)
- (";" tetris-rotate-prev)
- ("l" tetris-move-down)
- ("o" x-tetris-rotate-mirror)
- ("SPC" tetris-move-bottom)))
-
-(defun x-dired-in-current-directory ()
- "Open `dired' in the current directory."
- (interactive)
- (dired default-directory))
-
-(x-define-evil-bindings
- :normal
- ("d" x-dired-in-current-directory)
-
- :normal&visual
- ("a" x-evil-align-regexp)
- ("s" x-evil-sort-lines)
-
- :normal
- :prefix "g"
- ("s" magit-status))
-
-(with-eval-after-load 'eglot
- (x-define-evil-bindings
- :map eglot-mode-map
- :prefix "l"
- :normal
- ("a" eglot-code-actions)
- ("r" eglot-rename)))
-
-(use-package dired
- :ensure nil
- :config
- (evil-define-key 'normal dired-mode-map "n" #'find-file))
+ (delete-directory directory)))))
- ;;; Which-Key
-(use-package which-key
- :ensure nil
- :init
- (which-key-mode)
- :custom
- (which-key-dont-use-unicode nil)
- (which-key-ellipsis "..")
- (which-key-idle-delay 0.5))
-
- ;;; Context-Specific Modes
-(dolist (mode '(c-mode c-ts-mode go-ts-mode))
- (add-hook (x-mode-to-hook mode) #'electric-pair-local-mode)
- (add-hook (x-mode-to-hook mode) #'electric-quote-local-mode))
-
- ;;; Emacs Calculator
-(setq
- calc-display-trail nil
- calc-point-char ","
- calc-group-char "."
- calc-group-digits t)
-
- ;;; Emacs Tetris
-(use-package tetris
- :hook (tetris-mode . (lambda () (evil-local-mode -1))))
+(defun mm-find-file--remove-hooks ()
+ (remove-hook 'kill-buffer-hook
+ #'mm-find-file--maybe-delete-directories
+ :local)
+ (remove-hook 'after-save-hook
+ #'mm-find-file--remove-hooks
+ :local))
+
+(dolist (command #'(find-file find-alternate-file write-file))
+ (advice-add command :around #'mm-auto-create-directories))
+
+
+;;; Load Modules
+
+(require 'mm-abbrev) ; Text Expansion
+(require 'mm-calc) ; Emacs Calc
+(require 'mm-completion) ; Completions
+(require 'mm-dired) ; Dired
+(require 'mm-documentation) ; Documentation
+(require 'mm-editing) ; Text Editing
+(require 'mm-keybindings) ; Keybindings
+(require 'mm-lsp) ; Language Server Protocol
+(require 'mm-modeline) ; Modeline
+(require 'mm-projects) ; Project Management
+(require 'mm-tetris) ; Emacs Tetris
+(require 'mm-theme) ; Themeing
+(require 'mm-treesit) ; Tree-Sitter
+
+(when mm-darwin-p
+ (require 'mm-darwin)) ; MacOS
+
+
+;;; Postamble
+
+(message (emacs-init-time "Emacs initialized in %.2f seconds"))