summaryrefslogtreecommitdiff
path: root/.config
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
parentd452ae1347b3711bc0a7ac80cfa2c37d9d63836e (diff)
emacs: Overhaul configuration completely
Diffstat (limited to '.config')
-rw-r--r--.config/emacs/.gitignore2
-rw-r--r--.config/emacs/early-init.el170
-rw-r--r--.config/emacs/editing.el141
-rw-r--r--.config/emacs/init.el1160
-rw-r--r--.config/emacs/mango-theme.el119
-rw-r--r--.config/emacs/modules/mm-abbrev.el86
-rw-r--r--.config/emacs/modules/mm-calc.el25
-rw-r--r--.config/emacs/modules/mm-completion.el58
-rw-r--r--.config/emacs/modules/mm-darwin.el22
-rw-r--r--.config/emacs/modules/mm-dired.el9
-rw-r--r--.config/emacs/modules/mm-documentation.el48
-rw-r--r--.config/emacs/modules/mm-editing.el187
-rw-r--r--.config/emacs/modules/mm-keybindings.el112
-rw-r--r--.config/emacs/modules/mm-lsp.el41
-rw-r--r--.config/emacs/modules/mm-modeline.el115
-rw-r--r--.config/emacs/modules/mm-projects.el70
-rw-r--r--.config/emacs/modules/mm-tetris.el20
-rw-r--r--.config/emacs/modules/mm-theme.el222
-rw-r--r--.config/emacs/modules/mm-treesit.el140
-rw-r--r--.config/emacs/site-lisp/increment.el (renamed from .config/emacs/increment.el)2
-rw-r--r--.config/emacs/site-lisp/line-selection-mode.el18
-rw-r--r--.config/emacs/site-lisp/surround.el122
-rw-r--r--.config/emacs/templates9
23 files changed, 1841 insertions, 1057 deletions
diff --git a/.config/emacs/.gitignore b/.config/emacs/.gitignore
new file mode 100644
index 0000000..fb7fa52
--- /dev/null
+++ b/.config/emacs/.gitignore
@@ -0,0 +1,2 @@
+-*.el
+custom.el
diff --git a/.config/emacs/early-init.el b/.config/emacs/early-init.el
index dc8b6c2..d4ee43c 100644
--- a/.config/emacs/early-init.el
+++ b/.config/emacs/early-init.el
@@ -1,113 +1,109 @@
;;; early-init.el --- Emacs early init file -*- lexical-binding: t; -*-
-(defmacro x-set (&rest body)
- (declare (indent 0))
- (unless (zerop (% (length body) 2))
- (error "Uneven number of variable+value pairs"))
- (macroexp-progn
- (mapcar
- (lambda (pair)
- `(customize-set-variable ,(macroexp-quote (car pair)) ,(cadr pair)))
- (seq-split body 2))))
-
-(defconst 1-KiB 1024
- "The number of bytes in 1 kibibyte")
-
-(defconst 1-MiB (* 1-KiB 1024)
- "The number of bytes in 1 mebibyte.")
-
-(defconst 1-GiB (* 1-MiB 1024)
- "The number of bytes in 1 gibibyte.")
-
-(defconst x-cache-directory
+;;; XDG Base Directory Specification Compliance
+(defconst mm-cache-directory
(expand-file-name
"emacs"
(or (getenv "XDG_CACHE_HOME")
(expand-file-name ".cache" (getenv "HOME"))))
"The XDG-conformant cache directory that Emacs should use.")
-(defconst x-config-directory
+(defconst mm-config-directory
(expand-file-name
"emacs"
(or (getenv "XDG_CONFIG_HOME")
(expand-file-name ".config" (getenv "HOME"))))
"The XDG-conformant config directory that Emacs should use.")
-(defconst x-data-directory
+(defconst mm-data-directory
(expand-file-name
"emacs"
(or (getenv "XDG_DATA_HOME")
(expand-file-name ".local/share" (getenv "HOME"))))
"The XDG-conformant data directory that Emacs should use.")
-;; Create standard Emacs directories
-(dolist (dir (list x-cache-directory
- x-config-directory
- x-data-directory))
- (make-directory dir 'parents))
-
-(x-set
- user-emacs-directory x-cache-directory
- auto-save-list-file-prefix (expand-file-name
- "auto-save-list/" x-cache-directory)
- backup-directory-alist `(("." . ,(expand-file-name
- "backups" x-cache-directory))))
-
-(push x-config-directory load-path)
+(mapc (lambda (directory)
+ (make-directory directory :parents))
+ (list mm-cache-directory mm-config-directory mm-data-directory))
+(setopt user-emacs-directory mm-cache-directory
+ auto-save-list-file-prefix (expand-file-name
+ "auto-save-lismm-"
+ mm-cache-directory)
+ backup-directory-alist `(("." . ,(expand-file-name
+ "backups" mm-cache-directory))))
(when (native-comp-available-p)
(startup-redirect-eln-cache
- (expand-file-name (expand-file-name "eln/" x-cache-directory))))
-
-;; Temporarily set some variables to improve startup performance. We
-;; undo this in a following hook
-(let ((saved-file-name-handler-alist file-name-handler-alist)
- (saved-vc-handled-backends vc-handled-backends))
- (x-set
- gc-cons-threshold most-positive-fixnum
- gc-cons-percentage .5
- file-name-handler-alist nil
- vc-handled-backends nil)
- (add-hook 'emacs-startup-hook
- (lambda ()
- (x-set
- gc-cons-threshold (* 8 1-MiB)
- gc-cons-percentage 0.1
- file-name-handler-alist saved-file-name-handler-alist
- vc-handled-backends saved-vc-handled-backends))))
-
-(x-set read-process-output-max
- (let ((pipe-size-file "/proc/sys/fs/pipe-max-size"))
- (if (file-exists-p pipe-size-file)
- (with-temp-buffer
- (insert-file-contents pipe-size-file)
- (number-at-point))
- 1-MiB)))
-
-(menu-bar-mode -1)
+ (expand-file-name (expand-file-name "eln/" mm-cache-directory))))
+
+
+;;; Useful Constants
+
+(defconst mm-darwin-p (eq system-type 'darwin)
+ "This variable is non-nil if Emacs is running on a Darwin system.")
+
+
+;;; Basic Frame Settings
+
+(setopt frame-resize-pixelwise t
+ frame-inhibit-implied-resize t
+ ring-bell-function #'ignore
+ use-short-answers t
+ inhibit-splash-screen t
+ inhibit-startup-buffer-menu t)
+(if mm-darwin-p
+ (progn
+ (add-to-list 'default-frame-alist '(fullscreen . maximized))
+ (when (featurep 'ns)
+ (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))))
+ (menu-bar-mode -1))
(scroll-bar-mode -1)
(tool-bar-mode -1)
-(x-set
- frame-resize-pixelwise t
- frame-inhibit-implied-resize t
- frame-title-format '("%b")
- ring-bell-function #'ignore
- use-dialog-box t
- use-file-dialog nil
- use-short-answers t
- inhibit-splash-screen t
- inhibit-startup-screen t
- inhibit-x-resources t
- inhibit-startup-echo-area-message user-login-name
- inhibit-startup-buffer-menu t)
-
-;; Avoid the initial flash of white light when starting emacs
-(setq mode-line-format nil)
-(set-face-attribute 'default nil
- :background "#2B303B"
- :foreground "#C5C8C6")
-(set-face-attribute 'mode-line nil
- :background "#2B303B"
- :foreground "#C5C8C6"
- :box 'unspecified)
+
+;;; Startup Performance
+
+(setopt gc-cons-threshold most-positive-fixnum
+ gc-cons-percentage 0.5)
+(setopt read-process-output-max
+ (let ((pipe-size-file "/proc/sys/fs/pipe-max-size"))
+ (if (file-exists-p pipe-size-file)
+ (with-temp-buffer
+ (insert-file-contents pipe-size-file)
+ (number-at-point))
+ (* 1024 1024))))
+
+;; Set ‘file-name-handler-alist’ and ‘vc-handled-backends’ to nil
+;; temporarily and restore them over Emacs has properly initialized. We
+;; set threshold to 8 MiB which seems to be a good middleground for now.
+;; A higher threshold means less garbage collections but I’ve had issues
+;; with those garbage collections causing long freezes when they occur.
+(let ((saved-file-name-handler-alist file-name-handler-alist)
+ (saved-vc-handled-backends vc-handled-backends))
+ (setopt file-name-handler-alist nil
+ vc-handled-backends nil)
+ (add-hook
+ 'emacs-startup-hook
+ (defun mm-restore-emacs-settings ()
+ (setopt gc-cons-threshold (* 1024 1024 8)
+ gc-cons-percentage 0.1
+ file-name-handler-alist saved-file-name-handler-alist
+ vc-handled-backends saved-vc-handled-backends))))
+
+
+;;; Avoid Flashbang
+
+(setq-default mode-line-format nil) ; This will be set in init.el
+
+;; Colors taken from ‘mango-theme’
+(let ((background "#2B303B")
+ (foreground "#C5C8C6"))
+ (set-face-attribute
+ 'default nil
+ :background background
+ :foreground foreground)
+ (set-face-attribute
+ 'mode-line nil
+ :background background
+ :foreground foreground
+ :box 'unspecified))
diff --git a/.config/emacs/editing.el b/.config/emacs/editing.el
new file mode 100644
index 0000000..122570a
--- /dev/null
+++ b/.config/emacs/editing.el
@@ -0,0 +1,141 @@
+;;; editing.el --- Text editing commands -*- lexical-binding: t; -*-
+
+(defun e/align-regexp (regexp repeat)
+ "Align the marked region on REGEXP.
+When called interactively REGEXP is read from the minibuffer and the
+user is prompted about whether they would like to REPEAT the alignment.
+nn
+This function wraps `align-regexp' and implicitly prepends REGEXP with
+\"\\(\\s-*\\)\"."
+ (interactive
+ (list (concat "\\(\\s-*\\)"
+ (read-string
+ (format-prompt "Align regexp" nil)))
+ (y-or-n-p "Repeat?")))
+ (let ((start (min (mark) (point)))
+ (end (max (mark) (point))))
+ (align-regexp start end regexp 1 1 repeat)))
+
+(defun e/join-current-and-next-line ()
+ "Join the current- and next lines.
+Join the next line after point to the line point is currently on. This
+is in effect the reverse of `join-line'."
+ (interactive)
+ (delete-indentation 1))
+
+(defun e/transpose-previous-chars ()
+ "Transpose the two characters preceeding point.
+This command is similar to `transpose-chars' except it transposes the
+two characters that preceed the point instead of the characters that
+surround it."
+ (interactive)
+ (save-excursion
+ (backward-char)
+ (transpose-chars 1)))
+
+(defun e/transpose-current-and-next-lines ()
+ "Transpose the current and next lines.
+This command is similar to `transpose-lines' except it transposes the
+current and next lines instead of the current and previous lines. This
+maintains symmetry with `transpose-words'."
+ (interactive)
+ (let ((column (current-column)))
+ (forward-line)
+ (transpose-lines 1)
+ (forward-line -1)
+ (indent-region (pos-bol) (pos-eol))
+ (move-to-column column)))
+
+(defun e/mark-entire-word (&optional arg allow-extend)
+ "Mark ARG words beginning at point.
+This command is a wrapper around `mark-word' that moves the point such
+that the word under point is entirely marked. ARG and ALLOW-EXTEND are
+just as they are with `mark-word.'"
+ (interactive "P\np")
+ (if (eq last-command this-command)
+ (mark-word arg allow-extend)
+ (let ((bounds (bounds-of-thing-at-point 'word))
+ (numeric-arg allow-extend))
+ (if bounds
+ (goto-char (if (< numeric-arg 0)
+ (cdr bounds)
+ (car bounds)))
+ (forward-to-word (when (< numeric-arg 0) -1))))
+ (mark-word arg allow-extend)))
+
+(defun e/mark-entire-sexp (&optional arg allow-extend)
+ "Mark ARG sexps beginning at point.
+This command is a wrapper around `mark-sexp' that moves the point such
+that the sexp under point is entirely marked. ARG and ALLOW-EXTEND are
+just as they are with `mark-sexp.'"
+ (interactive "P\np")
+ (if (eq last-command this-command)
+ (mark-sexp arg allow-extend)
+ (let ((bounds (bounds-of-thing-at-point 'sexp))
+ (numeric-arg allow-extend))
+ (if bounds
+ (goto-char (if (< numeric-arg 0)
+ (cdr bounds)
+ (car bounds)))
+ (if (< numeric-arg 0)
+ (progn
+ (backward-sexp)
+ (forward-sexp))
+ (forward-sexp)
+ (backward-sexp))))
+ (mark-sexp arg allow-extend)))
+
+(defun e/mark-line-dwim (&optional arg)
+ "Mark ARG lines beginning at point.
+If the region is active then it is extended by ARG lines. If called
+without a prefix argument this command marks one line forwards unless
+point is ahead of the mark in which case this command marks one line
+backwards.
+
+If this function is called with a negative prefix argument and no region
+active, the current line is marked."
+ (interactive "P")
+ (let ((numeric-arg (prefix-numeric-value arg)))
+ (if (region-active-p)
+ (progn
+ (exchange-point-and-mark)
+ (goto-char (1+ (pos-eol (if arg numeric-arg
+ (when (< (point) (mark)) -1)))))
+ (exchange-point-and-mark))
+ (if (< numeric-arg 0)
+ (progn
+ (push-mark (pos-bol (+ 2 numeric-arg)) nil :activate)
+ (goto-char (1+ (pos-eol))))
+ (push-mark (1+ (pos-eol numeric-arg)) nil :activate)
+ (goto-char (pos-bol))))))
+
+(defun e/kill-ring-save-dwim ()
+ "Save the region as if killed, but don't kill it.
+This function is the same as `kill-ring-save' in Transient Mark mode,
+but when there is no active region it saves the line at point to the kill
+ring excluding any potential trailing newline."
+ (interactive)
+ (if (region-active-p)
+ (kill-ring-save -1 -1 :region)
+ (kill-ring-save (pos-bol) (pos-eol))))
+
+(defun e/scroll-down ()
+ "Scroll down one page.
+This function is identical to `cua-scroll-down' except it recenters the
+screeen after scrolling. If the user scrolls to the top of the document
+then no recentering occurs."
+ (interactive)
+ (let ((line-number (line-number-at-pos)))
+ (cua-scroll-down)
+ (when (= line-number (line-number-at-pos))
+ (goto-char (point-min)))
+ (recenter)))
+
+(defun e/scroll-up ()
+ "Scroll up one page.
+This function is identical to `cua-scroll-up' except it recenters the
+screen after scrolling."
+ (interactive)
+ (mm-do-and-center #'cua-scroll-up))
+
+(provide 'editing)
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"))
diff --git a/.config/emacs/mango-theme.el b/.config/emacs/mango-theme.el
index ff339bc..3771aca 100644
--- a/.config/emacs/mango-theme.el
+++ b/.config/emacs/mango-theme.el
@@ -1,16 +1,19 @@
;;; mango-theme.el --- Just your average dark theme -*- lexical-binding: t; -*-
(deftheme mango
- "Just another dark theme because none of the other options out there were just
-as I would like them. Why try to fix someone elses themes when I make my own?")
+ "Mildly dark, dark theme.
+Your average not-so-dark dark theme, because none of the other options
+were exactly to my liking. It’s about time I had a theme to call my
+own.")
-(defun mango-theme--get-color (name)
- "Get the RGB value of the color NAME from ‘mango-theme-palette’"
- (cadr (assq name mango-theme-palette)))
+(defun mango-theme--color (name)
+ "Get the RGB value of the color NAME from `mango-theme-colors-alist'."
+ (declare (pure t) (side-effect-free t))
+ (alist-get name mango-theme-colors-alist))
(defmacro mango-theme--generate-set-faces (&rest body)
"A macro to provide a much simpler syntax than what is expected by
-‘custom-theme-set-faces’. This is possible because I only run Emacs
+`custom-theme-set-faces'. This is possible because I only run Emacs
graphically, so I shouldn’t need to have multiple specs per face.
\(fn SPEC...)"
@@ -20,97 +23,103 @@ graphically, so I shouldn’t need to have multiple specs per face.
(add-to-list 'ret `(backquote ,(list (car spec) `((((type graphic)) ,(cdr spec)))))))
(reverse ret)))
-(defconst mango-theme-palette
- '((foreground "#C5C8C6")
- (background "#2B303B")
- (background-cool "#363C4A")
- (background-dark "#1D2635")
- (background-faint "#414859")
- (middleground "#4F5561")
- (disabled "#999999")
- (pale-azure "#9CDCFE")
- (celestial-blue "#569CD6")
- (violet "#E57AE5")
- (khaki "#F0E68C")
- (lime "#B8F182")
- (orange "#F1B282")
- (pink "#ED97F5")
- (spanish-red "#E60026"))
- "The color palette used throughout the ‘mango’ theme.")
+(defconst mango-theme-colors-alist
+ '((foreground . "#C5C8C6")
+ (background . "#2B303B")
+ (background-cool . "#363C4A")
+ (background-dark . "#1D2635")
+ (background-faint . "#414859")
+ (middleground . "#4F5561")
+ (disabled . "#999999")
+ (pale-azure . "#9CDCFE")
+ (celestial-blue . "#569CD6")
+ (violet . "#E57AE5")
+ (khaki . "#F0E68C")
+ (lime . "#B8F182")
+ (orange . "#F1B282")
+ (pink . "#ED97F5")
+ (spanish-red . "#E60026"))
+ "The color palette used throughout the `mango-theme'.")
(mango-theme--generate-set-faces
;; Standard Stuff
(default
- :foreground ,(mango-theme--get-color 'foreground)
- :background ,(mango-theme--get-color 'background))
+ :foreground ,(mango-theme--color 'foreground)
+ :background ,(mango-theme--color 'background))
(fringe
:inherit default)
;; Lines
(hl-line
- :background ,(mango-theme--get-color 'background-faint))
+ :background ,(mango-theme--color 'background-faint))
(region
- :background ,(mango-theme--get-color 'middleground))
+ :background ,(mango-theme--color 'middleground))
(header-line
- :background ,(mango-theme--get-color 'middleground))
- (mode-line
+ :background ,(mango-theme--color 'middleground))
+ (mode-line-active
:inherit header-line)
(mode-line-inactive
- :background ,(mango-theme--get-color 'background-cool)
+ :background ,(mango-theme--color 'background-cool)
:weight light)
-
+ (window-divider
+ :foreground ,(mango-theme--color 'background-cool))
+ (window-divider-first-pixel
+ :foreground ,(mango-theme--color 'background-cool))
+ (window-divider-last-pixel
+ :foreground ,(mango-theme--color 'background-cool))
+
;; Line Numbers
(line-number
- :foreground ,(mango-theme--get-color 'background-faint)
- :background ,(mango-theme--get-color 'background))
+ :foreground ,(mango-theme--color 'background-faint)
+ :background ,(mango-theme--color 'background))
(line-number-current-line
- :foreground ,(mango-theme--get-color 'orange)
- :background ,(mango-theme--get-color 'background)
+ :foreground ,(mango-theme--color 'orange)
+ :background ,(mango-theme--color 'background)
:weight bold)
;; Documentation
(font-lock-comment-face
- :foreground ,(mango-theme--get-color 'disabled))
+ :foreground ,(mango-theme--color 'disabled))
(font-lock-doc-face
:inherit font-lock-comment-face)
;; Core Language
(font-lock-keyword-face
- :foreground ,(mango-theme--get-color 'violet))
+ :foreground ,(mango-theme--color 'violet))
(font-lock-type-face
- :foreground ,(mango-theme--get-color 'celestial-blue))
+ :foreground ,(mango-theme--color 'celestial-blue))
(font-lock-builtin-face
:inherit font-lock-preprocessor-face)
;; Function-likes
(font-lock-function-name-face
- :foreground ,(mango-theme--get-color 'khaki))
+ :foreground ,(mango-theme--color 'khaki))
(font-lock-preprocessor-face
- :foreground ,(mango-theme--get-color 'pink)
+ :foreground ,(mango-theme--color 'pink)
:weight bold)
;; Variables
(font-lock-variable-name-face
- :foreground ,(mango-theme--get-color 'pale-azure))
+ :foreground ,(mango-theme--color 'pale-azure))
(font-lock-constant-face
:inherit font-lock-variable-name-face
:weight bold)
;; Other literals
(font-lock-number-face
- :foreground ,(mango-theme--get-color 'orange))
+ :foreground ,(mango-theme--color 'orange))
(help-key-binding
:inherit font-lock-constant-face)
;; Org Mode
(org-code
- :foreground ,(mango-theme--get-color 'orange))
+ :foreground ,(mango-theme--color 'orange))
(org-verbatim
- :foreground ,(mango-theme--get-color 'lime))
+ :foreground ,(mango-theme--color 'lime))
(org-block
- :background ,(mango-theme--get-color 'background-cool))
+ :background ,(mango-theme--color 'background-cool))
(org-hide
- :foreground ,(mango-theme--get-color 'background))
+ :foreground ,(mango-theme--color 'background))
(org-quote
:inherit org-block
:slant italic)
@@ -121,19 +130,19 @@ graphically, so I shouldn’t need to have multiple specs per face.
;; Magit
(magit-diff-hunk-heading
- :background ,(mango-theme--get-color 'background-cool))
+ :background ,(mango-theme--color 'background-cool))
(magit-diff-hunk-heading-highlight
- :background ,(mango-theme--get-color 'middleground))
+ :background ,(mango-theme--color 'middleground))
(magit-diff-context-highlight
:inherit hl-line)
(magit-section-highlight
:inherit hl-line)
(git-commit-summary
- :foreground ,(mango-theme--get-color 'khaki))
+ :foreground ,(mango-theme--color 'khaki))
(git-commit-overlong-summary
- :foreground ,(mango-theme--get-color 'foreground)
- :background ,(mango-theme--get-color 'spanish-red)
+ :foreground ,(mango-theme--color 'foreground)
+ :background ,(mango-theme--color 'spanish-red)
:weight bold)
;; Vertico
@@ -142,16 +151,16 @@ graphically, so I shouldn’t need to have multiple specs per face.
;; Marginalia
(marginalia-documentation
- :foreground ,(mango-theme--get-color 'disabled)
+ :foreground ,(mango-theme--color 'disabled)
:underline nil)
;; Tempel
(tempel-default
:slant italic
- :background ,(mango-theme--get-color 'middleground))
+ :background ,(mango-theme--color 'middleground))
(tempel-field
:slant italic
- :background ,(mango-theme--get-color 'middleground))
+ :background ,(mango-theme--color 'middleground))
(tempel-form
:slant italic
- :background ,(mango-theme--get-color 'middleground)))
+ :background ,(mango-theme--color 'middleground)))
diff --git a/.config/emacs/modules/mm-abbrev.el b/.config/emacs/modules/mm-abbrev.el
new file mode 100644
index 0000000..29ad72e
--- /dev/null
+++ b/.config/emacs/modules/mm-abbrev.el
@@ -0,0 +1,86 @@
+;;; mm-abbrev.el --- Emacs abbreviations and templates -*- lexical-binding: t; -*-
+
+;;; Helpers
+
+(defmacro mm-define-abbreviations (table &rest definitions)
+ "Define abbrevations for an abbreviation TABLE.
+Expand abbrev DEFINITIONS for the given TABLE. DEFINITIONS are a
+sequence of either string pairs mapping an abbreviation to its
+expansion, or a string and symbol pair mapping an abbreviation to a
+function."
+ (declare (indent 1))
+ (unless (cl-evenp (length definitions))
+ (user-error "expected an even-number of elements in DEFINITIONS"))
+ (macroexp-progn
+ (cl-loop for (abbrev expansion) in (seq-partition definitions 2)
+ if (stringp expansion)
+ collect (list #'define-abbrev table abbrev expansion)
+ else
+ collect (list #'define-abbrev table abbrev "" expansion))))
+
+
+;;; Abbreviation Configuration
+
+(use-package abbrev
+ :init
+ (setq-default abbrev-mode t)
+ :custom
+ (abbrev-file-name (expand-file-name "abbev-defs" mm-data-directory))
+ (save-abbrevs 'silently))
+
+
+;;; Abbreviation Definitions
+
+(defvar mm-c-mode-abbrev-table (make-abbrev-table)
+ "Abbreviations shared between `c-mode', `c++-mode', `c-ts-mode', and
+`c++-ts-mode'.")
+
+(mm-define-abbreviations mm-c-mode-abbrev-table
+ "flf" "flockfile"
+ "fpf" "fprintf"
+ "fuf" "funlockfile"
+ "pf" "printf"
+ "se" "stderr"
+ "si" "stdin"
+ "so" "stdout")
+
+(with-eval-after-load 'cc-mode
+ (setq c-mode-abbrev-table (copy-abbrev-table mm-c-mode-abbrev-table)
+ c++-mode-abbrev-table (copy-abbrev-table mm-c-mode-abbrev-table)))
+(with-eval-after-load 'c-ts-mode
+ (setq c-ts-mode-abbrev-table (copy-abbrev-table mm-c-mode-abbrev-table)
+ c++-ts-mode-abbrev-table (copy-abbrev-table mm-c-mode-abbrev-table)))
+
+(mm-define-abbreviations emacs-lisp-mode-abbrev-table
+ "ald" ";;;###autoload"
+ "gc" "goto-char"
+ "ins" "insert"
+ "nrt" "narrow-to-region"
+ "pmn" "point-min"
+ "pmx" "point-max"
+ "pnt" "point"
+ "rap" "region-active-p"
+ "rb" "region-beginning"
+ "re" "region-end"
+ "se" "save-excursion"
+ "sme" "save-mark-and-excursion"
+ "sr" "save-restriction")
+
+
+;;; Template Configuration
+
+(use-package tempel
+ :ensure t
+ :demand t
+ :pin gnu
+ :bind (:map tempel-map
+ ("TAB" . tempel-next)
+ ("S-TAB" . tempel-previous))
+ :custom
+ (tempel-trigger-prefix ",")
+ :init
+ (setopt tempel-path (expand-file-name "templates" mm-config-directory))
+ (add-hook 'completion-at-point-functions #'tempel-complete -10)
+ (add-to-list 'auto-mode-alist (cons tempel-path #'lisp-data-mode)))
+
+(provide 'mm-abbrev)
diff --git a/.config/emacs/modules/mm-calc.el b/.config/emacs/modules/mm-calc.el
new file mode 100644
index 0000000..2a9a74b
--- /dev/null
+++ b/.config/emacs/modules/mm-calc.el
@@ -0,0 +1,25 @@
+;;; mm-calc.el --- Emacs configurations for ‘calc-mode’ -*- lexical-binding: t; -*-
+
+;; TODO: Swap more than 2 elements?
+(defun mm-calc-swap ()
+ "Swap the top two elements on the stack."
+ (declare (modes calc-mode))
+ (interactive)
+ (calc-over 2)
+ (calc-truncate-up 2)
+ (calc-pop 1)
+ (calc-truncate-down 2))
+
+(use-package calc
+ :bind (:map calc-mode-map
+ ("C-c x" . #'mm-calc-swap))
+ ;; TODO: Can this be done in :custom?
+ :init
+ (setopt
+ calc-display-trail nil
+ calc-group-digits t
+ ;; Optimize for Europeans
+ calc-point-char ","
+ calc-group-char "."))
+
+(provide 'mm-calc)
diff --git a/.config/emacs/modules/mm-completion.el b/.config/emacs/modules/mm-completion.el
new file mode 100644
index 0000000..9ea7746
--- /dev/null
+++ b/.config/emacs/modules/mm-completion.el
@@ -0,0 +1,58 @@
+;;; mm-completion.el --- Configuration for Emacs completion -*- lexical-binding: t; -*-
+
+
+;;; Vertical Completions
+
+(use-package vertico
+ :ensure t
+ :custom
+ (vertico-cycle t)
+ :init
+ (vertico-mode)
+ :config
+ ;; Highlight the current line
+ (require 'hl-line))
+
+
+;;; Annotate Completions
+
+(use-package marginalia
+ :ensure t
+ :custom
+ (marginalia-field-width 50)
+ :init
+ (marginalia-mode))
+
+
+;;; Orderless Completion Style
+
+;; TODO: Make sure this doesn’t suck
+(use-package orderless
+ :ensure t
+ :custom
+ (completion-styles '(orderless basic))
+ (orderless-matching-styles '(orderless-prefixes))
+ (completion-category-overrides '((file (styles basic partial-completion)))))
+
+
+;;; Completion Popups
+
+(use-package corfu
+ :ensure t
+ :hook ((prog-mode . corfu-mode))
+ :bind (:map corfu-map
+ ("C-<return>" . newline))
+ :custom
+ (corfu-auto t)
+ (corfu-cycle t)
+ (corfu-auto-prefix 1)
+ (corfu-auto-delay 0))
+
+
+;;; Save Minibuffer History
+
+(use-package savehist-mode
+ :init
+ (savehist-mode))
+
+(provide 'mm-completion)
diff --git a/.config/emacs/modules/mm-darwin.el b/.config/emacs/modules/mm-darwin.el
new file mode 100644
index 0000000..79df610
--- /dev/null
+++ b/.config/emacs/modules/mm-darwin.el
@@ -0,0 +1,22 @@
+;;; mm-darwin.el --- MacOS Configuration -*- lexical-binding: t; -*-
+
+(unless (featurep 'ns)
+ (error "'NS not available. Something has gone horribly wrong."))
+
+
+;;; Launch Emacs Properly
+
+(defun mm-ns-raise-emacs ()
+ (ns-do-applescript "tell application \"Emacs\" to activate"))
+
+(add-hook
+ 'after-make-frame-functions
+ (defun mm-ns-raise-emacs-with-frame (frame)
+ (when (display-graphic-p)
+ (with-selected-frame frame
+ (mm-ns-raise-emacs)))))
+
+(when (display-graphic-p)
+ (mm-ns-raise-emacs))
+
+(provide 'mm-darwin)
diff --git a/.config/emacs/modules/mm-dired.el b/.config/emacs/modules/mm-dired.el
new file mode 100644
index 0000000..ada73a3
--- /dev/null
+++ b/.config/emacs/modules/mm-dired.el
@@ -0,0 +1,9 @@
+;;; mm-dired.el --- Configure the directory editor -*- lexical-binding: t; -*-
+
+(use-package dired
+ :hook ((dired-mode . dired-omit-mode)
+ (dired-mode . dired-hide-details-mode))
+ :bind (:map dired-mode-map
+ ("f" . find-file)))
+
+(provide 'mm-dired)
diff --git a/.config/emacs/modules/mm-documentation.el b/.config/emacs/modules/mm-documentation.el
new file mode 100644
index 0000000..473c766
--- /dev/null
+++ b/.config/emacs/modules/mm-documentation.el
@@ -0,0 +1,48 @@
+;;; mm-documentation.el --- Configuration related to documentation -*- lexical-binding: t; -*-
+
+
+;;; Display Available Keybindings
+
+(use-package which-key
+ :demand t
+ :config
+ (which-key-mode)
+ :custom
+ (which-key-dont-use-unicode nil)
+ (which-key-ellipsis "…")
+ (wihch-key-idle-delay .5))
+
+
+;;; Enhance Describe Commands
+
+(use-package helpful
+ :ensure t
+ :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))))
+
+
+;;; Open Manpage for Symbol
+
+(defun mm-documentation-man-at-point ()
+ "Open a UNIX manual page for the symbol at point."
+ (declare (modes (c-mode c++-mode c-ts-mode c++-ts-mode)))
+ (interactive)
+ (if-let ((symbol
+ (pcase major-mode
+ ((or 'c-mode 'c++-mode)
+ (thing-at-point 'symbol :no-properties))
+ ((or 'c-ts-mode 'c++-ts-mode)
+ (when-let ((node (treesit-thing-at-point "identifier" 'nested)))
+ (treesit-node-text node :no-properties))))))
+ (man symbol)
+ (message "No symbol at point.")))
+
+(dolist (mode '(c-mode c++-mode c-ts-mode c++-ts-mode))
+ (with-eval-after-load mode
+ (require 'man)))
+
+(provide 'mm-documentation)
diff --git a/.config/emacs/modules/mm-editing.el b/.config/emacs/modules/mm-editing.el
new file mode 100644
index 0000000..fb1475d
--- /dev/null
+++ b/.config/emacs/modules/mm-editing.el
@@ -0,0 +1,187 @@
+;;; mm-editing.el --- Text editing configuation -*- lexical-binding: t; -*-
+
+;;; Delete Region When Typing
+
+(use-package delsel
+ :init
+ (delete-selection-mode))
+
+
+;;; Force Spaces For Alignment
+
+(defun mm-editing-force-space-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)))
+
+(dolist (command #'(align c-backslash-region comment-dwim))
+ (advice-add command :around #'mm-editing-force-space-indentation))
+
+
+;;; Indentation Settings
+
+(setq-default
+ tab-width 4
+ indent-tabs-mode t)
+
+(defvar mm-editing-indentation-settings-alist
+ '((c-mode . (:extras c-basic-offset))
+ (c-ts-mode . (:extras c-ts-mode-indent-offset))
+ (css-mode . (:extras css-indent-offset))
+ (emacs-lisp-mode . (:width 8 :spaces t)) ; GNU code uses 8-column tabs
+ (go-ts-mode . (:extras go-ts-mode-indent-offset))
+ (go-mod-ts-mode . (:extras go-ts-mode-indent-offset))
+ (gsp-ts-mode . (:width 2 :extras gsp-ts-mode-indent-rules))
+ (helpful-mode . (:width 8)) ; GNU code uses 8-column tabs
+ (lisp-data-mode . (:spaces t))
+ (lisp-mode . (:spaces t))
+ (org-mode . (:spaces t))
+ (python-mode . (:extras python-indent-offset))
+ (python-ts-mode . (:extras python-indent-offset))
+ (sgml-mode . (:extras sgml-basic-offset))
+ (sh-mode . (:extras sh-basic-offset))
+ (vimscript-ts-mode . (:extras vimscript-ts-mode-indent-level)))
+ "Alist of indentation settings.
+Each pair in this alist is of the form (MODE . SETTINGS) where MODE
+specifies the mode for which the given SETTINGS should apply.
+
+SETTINGS is a plist of one-or-more of the following keys:
+
+ `:spaces' -- If nil force tabs for indentation, if non-nil for spaces
+ for indentation. If this key is not provided then the
+ value of `indent-tabs-mode' is used.
+ `:width' -- Specifies a non-negative number to be used as the tab
+ width and indentation offset. If this key is not
+ provided then the default value of `tab-width' is used.
+ `:extras' -- A list of mode-specific variables which control
+ indentation settings that need to be set for
+ configurations to properly be applied.")
+
+(defun mm-editing-set-indentation-settings ()
+ "Set indentation settings for the current major mode.
+The indentation settings are set based on the configured values in
+`mm-editing-indentation-settings-alist'."
+ (let* ((plist (alist-get major-mode mm-editing-indentation-settings-alist))
+ (spaces (plist-member plist :spaces))
+ (width (plist-member plist :width))
+ (extras (plist-member plist :extras)))
+ (when spaces
+ (indent-tabs-mode (and (cadr spaces) -1)))
+ (when width
+ (setq-local tab-width (cadr width)))
+ (when extras
+ (setq extras (cadr extras))
+ (when (symbolp extras)
+ (setq extras (list extras)))
+ (dolist (extra extras)
+ (set extra tab-width)))))
+
+(add-hook 'after-change-major-mode-hook #'mm-editing-set-indentation-settings)
+
+(defun mm-editing-set-tabsize ()
+ "Set the tabsize for the current buffer.
+If the current buffer’s major mode requires setting additional variables,
+those should be listed in `mm-editing-indentation-settings'."
+ (interactive)
+ (let* ((prompt-default (default-value 'tab-width))
+ (prompt (format-prompt "Tabsize" prompt-default))
+ (tabsize (mm-as-number (read-string prompt nil nil prompt-default))))
+ (setq-local tab-width tabsize)
+ (when-let* ((plist (alist-get major-mode mm-editing-indentation-settings))
+ (extras (plist-get plist :extras)))
+ (dolist (extra (if (symbolp extras)
+ (list extras)
+ extras))
+ (set (make-local-variable extra) tabsize)))))
+
+
+;;; Multiple Cursors
+
+(use-package multiple-cursors
+ :ensure t
+ :bind (("C->" . #'mc/mark-next-like-this)
+ ("C-<" . #'mc/mark-previous-like-this)
+ ("C-M-<" . #'mc/mark-all-like-this-dwim)
+ ("C-M->" . #'mc/edit-lines))
+ :init
+ (with-eval-after-load 'multiple-cursors-core
+ (dolist (command #'(delete-backward-char
+ delete-forward-char
+ backward-delete-char
+ capitalize-dwim
+ downcase-dwim
+ upcase-dwim))
+ (add-to-list 'mc/cmds-to-run-for-all command))
+ (dolist (command #'(helpful-callable
+ helpful-key
+ helpful-symbol
+ helpful-variable))
+ (add-to-list 'mc/cmds-to-run-once command))
+ (add-to-list 'mc/unsupported-minor-modes #'corfu-mode)))
+
+
+;;; Increment Numbers
+
+(use-package increment
+ :bind (("C-c i i" . #'increment-number-at-point)
+ ("C-c i d" . #'decrement-number-at-point))
+ :commands (increment-number-at-point decrement-number-at-point))
+
+
+;;; Surround With Delimeters
+
+(defun mm-editing-surround-with-spaces (char)
+ "Surrounds region or current symbol with a pair defined by CHAR.
+This is the same as `surround-insert' except it pads the contents of the
+surround with spaces."
+ (interactive
+ (list (char-to-string (read-char "Character: "))))
+ (let* ((pair (surround--make-pair char))
+ (left (car pair))
+ (right (cdr pair))
+ (bounds (surround--infer-bounds t)))
+ (save-excursion
+ (goto-char (cdr bounds))
+ (insert " " right)
+ (goto-char (car bounds))
+ (insert left " "))
+ (when (eq (car bounds) (point))
+ (forward-char))))
+
+;; TODO: Implement this manually
+(use-package surround
+ :ensure t
+ :bind-keymap ("M-'" . surround-keymap)
+ :bind (:map surround-keymap
+ ("S" . #'mm-editing-surround-with-spaces))
+ :config
+ (dolist (pair '(("‘" . "’")
+ ("“" . "”")
+ ("»" . "«")
+ ("⟮" . "⟯")))
+ (push pair surround-pairs))
+ (make-variable-buffer-local 'surround-pairs)
+ (add-hook 'emacs-lisp-mode-hook
+ (defun mm-editing-add-elisp-quotes-pair ()
+ (push '("`" . "'") surround-pairs))))
+
+
+;;; Emmet Mode
+
+(defun mm-editing-emmet-dwim (arg)
+ "Do-What-I-Mean Emmet expansion.
+If the region is active then the region will be surrounded by an emmet
+expansion read from the minibuffer. Otherwise the emmet expression
+before point is expanded. When provided a prefix argument the behaviour
+is as described by `emmet-expand-line'."
+ (interactive "P")
+ (if (region-active-p)
+ (call-interactively #'emmet-wrap-with-markup)
+ (emmet-expand-line arg)))
+
+(use-package emmet-mode
+ :ensure t
+ :bind ("C-," . mm-editing-emmet-dwim))
+
+(provide 'mm-editing)
diff --git a/.config/emacs/modules/mm-keybindings.el b/.config/emacs/modules/mm-keybindings.el
new file mode 100644
index 0000000..d01b447
--- /dev/null
+++ b/.config/emacs/modules/mm-keybindings.el
@@ -0,0 +1,112 @@
+;;; mm-keybindings.el --- Emacs keybindings -*- lexical-binding: t; -*-
+
+(require 'editing)
+
+;; The following keys are either unbound and are free to populate, or are
+;; bound to functions I don’t care for:
+;; ‘C-i’, ‘C-j’, ‘C-o’, ‘C-.’, ‘C-{’, ‘C-}’, ‘C-|’, ‘C-/’, ‘C-\;’, ‘C-:’
+
+
+;;; Helper Macros
+
+(defmacro mm-keymap-set (keymap &rest definitions)
+ (declare (indent 1))
+ (unless (cl-evenp (length definitions))
+ (user-error "Expected an even-number of elements in DEFINITIONS."))
+ `(cl-loop for (from to) on (list ,@definitions) by #'cddr
+ do (keymap-set ,keymap from to)))
+
+(defmacro mm-keymap-set-repeating (keymap &rest definitions)
+ (declare (indent 1))
+ (unless (cl-evenp (length definitions))
+ (user-error "Expected an even-number of elements in DEFINITIONS."))
+ (let ((keymap-gen (gensym "mm-keybindings--repeat-map-")))
+ `(progn
+ (defvar-keymap ,keymap-gen)
+ (cl-loop for (from to) on (list ,@definitions) by #'cddr
+ do (progn
+ (keymap-set ,keymap-gen from to)
+ (put to 'repeat-map ',keymap-gen))))))
+
+(defmacro mm-keymap-remap (keymap &rest commands)
+ "Define command remappings for a given KEYMAP.
+COMMANDS is a sequence of unquoted commands. For each pair of COMMANDS
+the first command is remapped to the second command."
+ (declare (indent 1))
+ (unless (cl-evenp (length commands))
+ (user-error "Expected an even-number of elements in COMMANDS."))
+ (macroexp-progn
+ (cl-loop for (from to) in (seq-partition commands 2)
+ collect `(keymap-set
+ ,keymap
+ ,(concat "<remap> <" (symbol-name from) ">")
+ #',to))))
+
+
+;;; Disable ESC as Meta
+
+(keymap-global-set "<escape>" #'ignore)
+
+
+;;; Enable Repeat Bindings
+
+(use-package repeat
+ :init
+ (repeat-mode))
+
+
+;;; Remap Existing Bindings
+
+(mm-keymap-remap global-map
+ backward-delete-char-untabify backward-delete-char
+ kill-ring-save e/kill-ring-save-dwim
+
+ capitalize-word capitalize-dwim
+ downcase-word downcase-dwim
+ upcase-word upcase-dwim
+
+ mark-word e/mark-entire-word
+ mark-sexp e/mark-entire-sexp
+
+ transpose-chars e/transpose-previous-chars
+ transpose-lines e/transpose-current-and-next-lines)
+
+(with-eval-after-load 'cc-vars
+ (setopt c-backspace-function #'backward-delete-char))
+
+
+;;; Remove Unwanted Bindings
+
+(keymap-global-unset "C-x C-c" :remove) ; ‘capitalize-region’
+(keymap-global-unset "C-x C-l" :remove) ; ‘downcase-region’
+(keymap-global-unset "C-x C-u" :remove) ; ‘upcase-region’
+
+
+;;; Bind Commands Globally
+
+(mm-keymap-set global-map
+ "<next>" #'e/scroll-up
+ "<prior>" #'e/scroll-down
+ "C-<next>" #'forward-page
+ "C-<prior>" #'backward-page
+
+ "C-." #'repeat
+ "C-/" #'e/mark-line-dwim
+
+ "C-c d" #'duplicate-dwim
+ "C-c t a" #'e/align-regexp
+ "C-c t f" #'fill-paragraph
+ "C-c t s" #'sort-lines
+ "C-c j" #'e/join-current-and-next-line
+ "C-c J" #'join-line)
+
+(mm-keymap-set-repeating global-map
+ "j" #'e/join-current-and-next-line
+ "J" #'join-line)
+
+(with-eval-after-load 'increment
+ (mm-keymap-set-repeating global-map
+ "d" #'decrement-number-at-point
+ "i" #'increment-number-at-point))
+
+(provide 'mm-keybindings)
diff --git a/.config/emacs/modules/mm-lsp.el b/.config/emacs/modules/mm-lsp.el
new file mode 100644
index 0000000..2e0feac
--- /dev/null
+++ b/.config/emacs/modules/mm-lsp.el
@@ -0,0 +1,41 @@
+;;; mm-lsp.el --- Language Server Protocol configuration -*- lexical-binding: t; -*-
+
+;;; Configure LSP
+
+(defun mm-lsp-eglot-no-inlay-hints ()
+ "Disable inlay hints when `eglot' is enabled."
+ (eglot-inlay-hints-mode -1))
+
+(use-package eglot
+ :hook (((c-mode c-ts-mode
+ c++-mode c++-ts-mode
+ go-ts-mode
+ python-mode python-ts-mode
+ js-mode js-ts-mode)
+ . eglot-ensure)
+ (eglot-managed-mode . mm-lsp-eglot-no-inlay-hints))
+ :init
+ (fset #'jsonrpc--log-event #'ignore)
+ :custom
+ (eglot-events-buffer 0)
+ (eglot-extend-to-xref t)
+ :config
+ (add-to-list 'eglot-stay-out-of 'flymake)
+ (add-to-list 'eglot-server-programs
+ '((c-mode c-ts-mode c++-mode c++-ts-mode)
+ . ("clangd" "--header-insertion=never"))))
+
+
+;;; Use Tempel for Snippets
+
+(defun mm-lsp-eglot-tempel-enable ()
+ "Enable `eglot-tempel-mode'.
+If `eglot-tempel-mode' is already enabled this function does nothing."
+ (unless (default-value eglot-tempel-mode)
+ (eglot-tempel-mode)))
+
+(use-package eglot-tempel
+ :after eglot
+ :hook (eglot-managed-mode . mm-lsp-eglot-tempel-enable))
+
+(provide 'mm-lsp)
diff --git a/.config/emacs/modules/mm-modeline.el b/.config/emacs/modules/mm-modeline.el
new file mode 100644
index 0000000..cd9af3e
--- /dev/null
+++ b/.config/emacs/modules/mm-modeline.el
@@ -0,0 +1,115 @@
+;;; mm-modeline.el --- Pluggable modeline components -*- lexical-binding: t; -*-
+
+(defmacro mm-modeline--define-component (name &rest forms)
+ (declare (indent 1))
+ `(progn
+ (defvar-local ,name '(:eval (or ,(macroexp-progn forms) "")))
+ (put ',name 'risky-local-variable t)))
+
+
+;;; Faces
+
+(defface mm-modeline-narrow-face
+ '((t :foreground "#C5C8C6" ; From ‘mango-theme’
+ :background "dark red"
+ :box "dark red"
+ :weight bold))
+ "Face for the `mm-modeline-narrow' modeline component.")
+
+
+;;; Support Icons
+
+(use-package all-the-icons
+ :ensure t
+ :init
+ (defvar mm-all-the-icons-cookie
+ (expand-file-name ".all-the-icons-installed-p" mm-cache-directory))
+ (unless (file-exists-p mm-all-the-icons-cookie)
+ (all-the-icons-install-fonts)
+ (make-empty-file mm-all-the-icons-cookie :parents))
+ (set-char-table-range char-width-table #xE907 2))
+
+
+;;; Modeline Components
+
+(mm-modeline--define-component mm-modeline-readonly
+ (when buffer-read-only
+ (propertize " READONLY" 'face 'bold)))
+
+(mm-modeline--define-component mm-modeline-buffer-name
+ (propertize "%b" 'face 'font-lock-constant-face))
+
+(mm-modeline--define-component mm-modeline-buffer-modified
+ (when (and (buffer-modified-p)
+ (buffer-file-name))
+ (propertize " (modified)" 'face 'shadow)))
+
+(mm-modeline--define-component mm-modeline-major-mode-name
+ (propertize
+ (thread-last
+ major-mode
+ (symbol-name)
+ (capitalize)
+ (string-replace "-" " ")
+ (string-replace "Ts Mode" "Tree-Sitter Mode")
+ ;; Casing doesn’t work for abbreviations, so fix it manually
+ (replace-regexp-in-string "\\<\\(M\\)?html\\>" "\\1HTML")
+ (replace-regexp-in-string "\\<\\(S\\)?css\\>" "\\1CSS")
+ (replace-regexp-in-string "\\<toml\\>" "TOML")
+ (replace-regexp-in-string "\\<gsp\\>" "GSP"))
+ 'face 'bold))
+
+(mm-modeline--define-component mm-modeline-major-mode-symbol
+ (propertize
+ (cond
+ ((derived-mode-p 'prog-mode) "λ ")
+ ((derived-mode-p '(text-mode conf-mode)) "§ ")
+ ((derived-mode-p 'comint-mode) ">_ ")
+ (t ""))
+ 'face 'shadow))
+
+(mm-modeline--define-component mm-modeline-narrow
+ (when (buffer-narrowed-p)
+ (propertize
+ " Narrow "
+ 'face 'mm-modeline-narrow-face)))
+
+(mm-modeline--define-component mm-modeline-git-branch
+ (when-let ((branch (car (and (featurep 'vc-git)
+ (vc-git-branches)))))
+ (concat
+ (propertize "\uE907" 'display '(raise 0))
+ " "
+ (propertize branch 'face 'font-lock-constant-face)
+ " │ ")))
+
+
+;;; Padding Between Left and Right
+
+(mm-modeline--define-component mm-modeline-left-right-padding
+ (let ((length (string-width (format-mode-line mm-modeline-right))))
+ (propertize " " 'display `(space :align-to (- right ,length)))))
+
+
+;;; Configure Modeline
+
+(setopt mode-line-format-right-align 'right-margin)
+
+(setq
+ mm-modeline-left (list mm-modeline-narrow
+ mm-modeline-readonly
+ " "
+ mm-modeline-buffer-name
+ mm-modeline-buffer-modified
+ " │ "
+ mm-modeline-major-mode-symbol
+ mm-modeline-major-mode-name
+ mm-modeline-left-right-padding
+ mode-line-end-spaces)
+ mm-modeline-right (list mm-modeline-git-branch
+ "%l:%c "))
+
+(setq-default
+ mode-line-format
+ (list mm-modeline-left mm-modeline-left-right-padding mm-modeline-right))
+(provide 'mm-modeline)
diff --git a/.config/emacs/modules/mm-projects.el b/.config/emacs/modules/mm-projects.el
new file mode 100644
index 0000000..fad56ae
--- /dev/null
+++ b/.config/emacs/modules/mm-projects.el
@@ -0,0 +1,70 @@
+;;; mm-projects.el --- Configuration for project management -*- lexical-binding: t; -*-
+
+;;; Project Configuration
+
+(defun mm-projects-project-magit-status ()
+ "Open a Git status buffer for the current project.
+This is intended to be called interactively via
+ `project-switch-commands'."
+ (interactive)
+ (thread-last
+ (project-current t)
+ (project-root)
+ (magit-status-setup-buffer)))
+
+(use-package project
+ :defer 1 ; Marginal startup performance improvement
+ :custom
+ (project-switch-commands '((project-dired "Dired" ?d)
+ (project-find-file "Find File")
+ (project-find-regexp "Find Regexp")
+ (mm-projects-project-magit-status "Git Status" ?s)))
+ :config
+ (unless mm-darwin-p
+ (if-let ((repo-directory (getenv "REPODIR")))
+ (mm-with-suppressed-output
+ (thread-last
+ (directory-files repo-directory :full "\\`[^.]")
+ (mapcar (lambda (path) (concat path "/"))) ; Avoid duplicate entries
+ (mapc #'project-remember-projects-under)))
+ (warn "The REPODIR environment variable is not set."))))
+
+
+;;; Git Client
+
+(use-package magit
+ :ensure t
+ :custom
+ (transient-default-level 7)
+ (magit-display-buffer-function
+ #'magit-display-buffer-same-window-except-diff-v1)
+ :config
+ (transient-define-suffix mm-projects-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 remotes" mm-projects-magit-push-current-to-all-remotes)))
+
+(use-package magit-todos
+ :ensure t
+ :after magit
+ :hook magit-mode
+ :custom
+ (magit-todos-exclude-globs '("vendor/")))
+
+
+;; Project Compilation
+
+(use-package compile
+ :config
+ (require 'ansi-color)
+ (add-hook 'compilation-filter-hook #'ansi-color-compilation-filter))
+
+(provide 'mm-projects)
diff --git a/.config/emacs/modules/mm-tetris.el b/.config/emacs/modules/mm-tetris.el
new file mode 100644
index 0000000..06fae58
--- /dev/null
+++ b/.config/emacs/modules/mm-tetris.el
@@ -0,0 +1,20 @@
+;;; mm-tetris.el --- Emacs configurations for ‘tetris’ -*- lexical-binding: t; -*-
+
+(defun mm-tetris-rotate-mirror ()
+ "Rotate the current piece by 180°."
+ (declare (modes tetris-mode))
+ (interactive)
+ (tetris-rotate-next)
+ (tetris-rotate-next))
+
+(use-package tetris
+ :bind (:map tetris-mode-map
+ ("a" . tetris-move-left)
+ ("d" . tetris-move-right)
+ ("k" . tetris-rotate-next)
+ (";" . tetris-rotate-prev)
+ ("l" . tetris-move-down)
+ ("o" . mm-tetris-rotate-mirror)
+ ("SPC" . tetris-move-bottom)))
+
+(provide 'mm-tetris)
diff --git a/.config/emacs/modules/mm-theme.el b/.config/emacs/modules/mm-theme.el
new file mode 100644
index 0000000..8aa82cd
--- /dev/null
+++ b/.config/emacs/modules/mm-theme.el
@@ -0,0 +1,222 @@
+;;; mm-theme.el --- Emacs theme settings -*- lexical-binding: t; -*-
+
+
+;;; Custom Theme
+
+(load-theme 'mango :no-confirm)
+
+
+;;; Fonts
+
+(defvar mm-theme-monospace-font `(,(if mm-darwin-p
+ "Iosevka Custom"
+ "Iosevka Smooth")
+ :weight regular
+ :height 162)
+ "The default monospace font.
+This is a plist containing a font name, -weight, and -height.")
+
+(defvar mm-theme-proportional-font '("SF Pro" :weight regular :height 162)
+ "The default proportional font.
+This is a plist containing a font name, -weight, and -height.")
+
+(defun mm-theme-set-fonts (&optional _frame)
+ "Set frame font settings.
+Sets the frame font settings according to the fonts specified by
+`mm-theme-monospace-font' and `mm-theme-proportional-font'.
+
+This function can be used as a hook in `after-make-frame-functions' and
+_FRAME is ignored."
+ (interactive)
+ (let* ((mono-family (car mm-theme-monospace-font))
+ (mono-props (cdr mm-theme-monospace-font))
+ (prop-family (car mm-theme-proportional-font))
+ (prop-props (cdr mm-theme-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)))
+ ;; Some characters in this font are larger than usual
+ (when (string= mono-family "Iosevka Smooth")
+ (dolist (rune '(?… ?— ?← ?→ ?⇐ ?⇒ ?⇔))
+ (set-char-table-range char-width-table rune 2)))
+ (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 #'mm-theme-set-fonts)
+ (mm-theme-set-fonts))
+
+;; Ligature Settings
+
+(defvar mm-theme-ligatures-alist
+ `(((c-mode c-ts-mode c++-mode c++-ts-mode)
+ . ("->"))
+ ((c++-mode c++-ts-mode)
+ . ("::"))
+ ((js-mode js-ts-mode typescript-ts-mode vue-ts-mode)
+ . (("=" ,(rx (or ?> (** 1 2 ?=))))
+ ("!" ,(rx (** 1 2 ?=)))))
+ (go-ts-mode
+ . (":=" "<-"))
+ ((python-mode python-ts-mode)
+ . (":=" "->"))
+ ((mhtml-mode html-mode html-ts-mode vue-ts-mode)
+ . ("<!--" "-->" "/>"))
+ (prog-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 its 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 mm-theme-update-ligatures ()
+ "Update the ligature composition tables.
+After running this function you may need to restart `ligature-mode'.
+
+Also see `mm-theme-ligatures-alist'."
+ (interactive)
+ (setopt ligature-composition-table nil)
+ (cl-loop for (spec . ligatures) in mm-theme-ligatures-alist
+ do (ligature-set-ligatures spec ligatures)))
+
+(use-package ligature
+ :ensure t
+ :if (and (or mm-darwin-p
+ (seq-contains-p (split-string system-configuration-features)
+ "HARFBUZZ"))
+ (display-graphic-p))
+ :commands ligature-mode
+ :init
+ ;; Add ‘ligature-mode’ as a hook for all modes configured in
+ ;; ‘mm-theme-ligatures-alist’
+ (thread-last
+ mm-theme-ligatures-alist
+ (mapcar #'car)
+ (flatten-tree)
+ (seq-uniq)
+ (mapcar #'mm-mode-to-hook)
+ (mapc (lambda (mode) (add-hook mode #'ligature-mode))))
+ :config
+ (mm-theme-update-ligatures))
+
+
+;;; Background Opacity
+
+(defvar mm-theme-background-opacity 100
+ "Opacity of the graphical Emacs frame.
+A value of 0 is fully transparent while 100 is fully opaque.")
+
+(defun mm-theme-background-opacity (opacity)
+ "Set the current frames' background opacity.
+See also the `mm-theme-background-opacity' variable."
+ (interactive
+ (list (mm-as-number
+ (read-string
+ (format-prompt "Background opacity"
+ (default-value 'mm-theme-background-opacity))
+ nil nil mm-theme-background-opacity))))
+ (set-frame-parameter nil 'alpha-background opacity))
+
+(add-to-list
+ 'default-frame-alist (cons 'alpha-background mm-theme-background-opacity))
+
+
+;;; Divider Between Windows
+
+(use-package frame
+ :init
+ (window-divider-mode))
+
+
+;;; Pulse Line on Jump
+
+(use-package pulsar
+ :ensure t
+ :demand t
+ :custom
+ (pulsar-pulse t)
+ (pulsar-delay .05)
+ (pulsar-iterations 10)
+ :config
+ (add-to-list 'pulsar-pulse-functions #'jump-to-register)
+ (add-to-list 'pulsar-pulse-functions #'e/scroll-up)
+ (add-to-list 'pulsar-pulse-functions #'e/scroll-down)
+ ;; Integrate with ‘compilation-mode’
+ (add-hook 'next-error-hook #'pulsar-pulse-line)
+ (pulsar-global-mode)
+ :hook
+ ((next-error . (pulsar-pulse-line-red
+ pulsar-recenter-top
+ pulsar-reveal-entry))
+ (minibuffer-setup . pulsar-pulse-line-red))
+ :bind
+ (("C-c h l" . pulsar-highlight-dwim)))
+
+
+;;; Add Padding
+
+(use-package spacious-padding
+ :ensure t
+ :demand t
+ :config
+ (spacious-padding-mode))
+
+
+;;; Pretty Page Boundaries
+
+(use-package page-break-lines
+ :ensure t
+ :demand t
+ :init
+ (add-hook
+ 'change-major-mode-hook
+ (defun mm-theme--set-page-break-max-width ()
+ (setopt page-break-lines-max-width fill-column)))
+ (global-page-break-lines-mode)
+ :config
+ ;; Since the ‘^L’ character is replaced by a horizontal rule, the
+ ;; cursor should appear below the horizontal rule. When moving
+ ;; backwards we need to account for the fact that the cursor is
+ ;; actually one character ahead of hte page break and adjust
+ ;; accordingly.
+ (advice-add
+ #'forward-page :after
+ (defun mm-theme--forward-char (&rest _)
+ (forward-char)))
+ (advice-add
+ #'backward-page :before
+ (defun mm-theme--backward-char (&rest _)
+ (backward-char))))
+
+
+;;; More Intuiative UI for Certain Modes
+
+(use-package line-selection-mode
+ :hook ((dired-mode . line-selection-mode)
+ (ibuffer-mode . line-selection-mode)))
+
+(provide 'mm-theme)
diff --git a/.config/emacs/modules/mm-treesit.el b/.config/emacs/modules/mm-treesit.el
new file mode 100644
index 0000000..75e679f
--- /dev/null
+++ b/.config/emacs/modules/mm-treesit.el
@@ -0,0 +1,140 @@
+;;; mm-treesit.el --- Tree-Sitter configuration -*- lexical-binding: t; -*-
+
+(unless (treesit-available-p)
+ (error "Tree-Sitter is not available."))
+
+
+;;; Tree-Sitter Variables
+
+(defvar mm-treesit-language-remap-alist
+ '((cpp . c++)
+ (gomod . go-mod)
+ (javascript . js)
+ (vim . vimscript))
+ "TODO")
+
+(setopt treesit-font-lock-level 4)
+(setopt treesit-language-source-alist
+ '((c "https://github.com/tree-sitter/tree-sitter-c")
+ (cpp "https://github.com/tree-sitter/tree-sitter-cpp")
+ (css "https://github.com/tree-sitter/tree-sitter-css")
+ (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")
+ (python "https://github.com/tree-sitter/tree-sitter-python")
+ (typescript "https://github.com/tree-sitter/tree-sitter-typescript"
+ "master" "typescript/src")
+ (vim "https://github.com/tree-sitter-grammars/tree-sitter-vim")
+ (vue "https://github.com/ikatyang/tree-sitter-vue")))
+
+
+;;; Install Missing Parsers
+
+(defun mm-treesit-sync-sources ()
+ "Sync Tree-Sitter parsers.
+Reinstall the Tree-Sitter parsers specified by
+ `treesit-language-source-alist'."
+ (interactive)
+ (let ((total (length treesit-language-source-alist))
+ (count 0)
+ (work treesit-language-source-alist)
+ (processors-to-use (max 1 (1- (num-processors)))))
+ (while work
+ (let ((specs (seq-take work processors-to-use)))
+ (dolist (spec specs)
+ (async-start
+ `(lambda ()
+ ,(async-inject-variables "\\`treesit-language-source-alist\\'")
+ (treesit-install-language-grammar ',(car spec)))
+ (lambda (_)
+ (setq count (1+ count))
+ (message "Done syncing Tree-Sitter grammar for `%s' [%d/%d]"
+ (car spec) count total))))
+ (setq work (seq-drop work processors-to-use))))))
+
+(thread-last
+ (mapcar #'car treesit-language-source-alist)
+ (seq-remove #'treesit-language-available-p)
+ (mapc #'treesit-install-language-grammar))
+
+
+;;; Install Additional TS Modes
+
+(use-package gsp-ts-mode
+ :vc (:url "https://git.thomasvoss.com/gsp-ts-mode"
+ :branch "master"
+ :rev :newest
+ :vc-backend Git)
+ :ensure t)
+
+;; NOTE: This package doesn’t autoload its ‘auto-mode-alist’ entries
+(use-package vimscript-ts-mode
+ :ensure t
+ :mode (rx (or (seq (? (or ?. ?_)) (? ?g) "vimrc")
+ ".vim"
+ ".exrc")
+ eos))
+
+;; NOTE: This package doesn’t autoload its ‘auto-mode-alist’ entries
+(use-package vue-ts-mode
+ :vc (:url "https://github.com/8uff3r/vue-ts-mode.git"
+ :branch "main"
+ :rev :newest
+ :vc-backend Git)
+ :ensure t
+ :mode "\\.vue\\'")
+
+
+;;; Prefer Tree-Sitter Modes
+
+;; NOTE: ‘go-ts-mode’ already adds itself to ‘auto-mode-alist’ but it
+;; isn’t autoloaded as of 2024-09-29 so we need to do it ourselves
+;; anyway. Same goes for ‘typescript-ts-mode’.
+(defvar mm-treesit-language-file-name-alist
+ '((go . "\\.go\\'")
+ (go-mod . "/go\\.mod\\'")
+ (typescript . "\\.ts\\'"))
+ "Alist mapping languages to their associated file-names.
+This alist is a set of pairs of the form (LANG . REGEXP) where LANG is
+the symbol corresponding to a major mode with the ‘-ts-mode’ suffix
+removed. REGEXP is a regular expression matching filenames for which
+the associated language’s major-mode should be enabled.
+
+This alist is used to configure `auto-mode-alist'.")
+
+(dolist (spec treesit-language-source-alist)
+ (let* ((lang (car spec))
+ (lang (alist-get lang mm-treesit-language-remap-alist lang))
+ (symbol-name (symbol-name lang))
+ (name-mode (intern (concat symbol-name "-mode")))
+ (name-ts-mode (intern (concat symbol-name "-ts-mode"))))
+ ;; If ‘name-ts-mode’ is already in ‘auto-mode-alist’ then we don’t
+ ;; need to do anything, however if that’s not the case then if
+ ;; ‘name-ts-mode’ and ‘name-mode’ are both bound we do a simple
+ ;; remap. If the above is not true then we lookup the extensions in
+ ;; ‘mm-treesit-language-file-name-alist’.
+ (cond
+ ((not (fboundp name-ts-mode))
+ (warn "`%s' is missing." name-ts-mode))
+ ((rassq name-ts-mode auto-mode-alist)
+ nil)
+ ((fboundp name-mode)
+ (add-to-list 'major-mode-remap-alist (cons name-mode name-ts-mode)))
+ (t ; (and (fboundp name-ts-mode) (not (fboundp name-mode)))
+ (if-let ((file-regexp
+ (alist-get lang mm-treesit-language-file-name-alist)))
+ (add-to-list 'auto-mode-alist (cons file-regexp name-ts-mode))
+ (warn "Unable to determine the extension for `%s'." name-ts-mode))))))
+
+
+;;; Hack For C23
+
+(advice-add #'c-ts-mode--keywords :filter-return
+ (defun mm-c-ts-mode-add-constexpr (keywords)
+ ;; NOTE: We can’t add ‘typeof’ until it’s added to the TS grammar
+ ;; https://github.com/tree-sitter/tree-sitter-c/issues/236
+ (append keywords '("constexpr"))))
+
+(provide 'mm-treesit)
diff --git a/.config/emacs/increment.el b/.config/emacs/site-lisp/increment.el
index 801ae4e..b5bea53 100644
--- a/.config/emacs/increment.el
+++ b/.config/emacs/site-lisp/increment.el
@@ -46,7 +46,7 @@
(defun increment--number-to-binary-string (number)
(nreverse
- (cl-loop for x = number then (lsh x -1)
+ (cl-loop for x = number then (ash x -1)
while (not (= x 0))
concat (if (= 0 (logand x 1)) "0" "1"))))
diff --git a/.config/emacs/site-lisp/line-selection-mode.el b/.config/emacs/site-lisp/line-selection-mode.el
new file mode 100644
index 0000000..83da013
--- /dev/null
+++ b/.config/emacs/site-lisp/line-selection-mode.el
@@ -0,0 +1,18 @@
+;;; line-selection-mode.el --- Minor mode for selection by lines -*- lexical-binding: t; -*-
+
+(defvar-local line-selection-mode--cursor-type nil)
+
+;;;###autoload
+(define-minor-mode line-selection-mode
+ "Enable `hl-line-mode' and hide the current cursor."
+ :global nil
+ :init-value nil
+ (if line-selection-mode
+ (progn
+ (hl-line-mode)
+ (setq line-selection-mode--cursor-type cursor-type)
+ (setq-local cursor-type nil))
+ (hl-line-mode -1)
+ (setq-local cursor-type line-selection-mode--cursor-type)))
+
+(provide 'line-selection-mode)
diff --git a/.config/emacs/site-lisp/surround.el b/.config/emacs/site-lisp/surround.el
new file mode 100644
index 0000000..8fdf443
--- /dev/null
+++ b/.config/emacs/site-lisp/surround.el
@@ -0,0 +1,122 @@
+;;; surround.el --- Surround a region with delimeters -*- lexical-binding: t; -*-
+
+(require 'cl-macs)
+
+(defgroup surround nil
+ "Customization group for `surround'."
+ :group 'convenience)
+
+(defcustom surround-with-paired-bracket-p t
+ "Surround text with paired brackets.
+If non-nil surrounding text with a character (assuming that character is
+not configured in `surround-pairs-alist') will attempt to surround the
+text with the supplied character and its paired bracket.
+
+As an example, if `surround-with-paired-bracket-p' is non-nil and the
+user attempts to surround the word “foo” with the character “「” the
+result would be “「foo」”.
+
+Whether or not an opening- or closing bracket is provided is not
+important; the opening bracket will always be placed at the front of the
+region and the closing bracket at the end of the region (assuming
+left-to-right writing).
+
+In more technical terms this function surrounds text with both the
+provided character and the characters corresponding Bidi_Paired_Bracket
+Unicode property."
+ :type 'boolean
+ :package-version '(surround . "1.0.0")
+ :group 'surround)
+
+(defcustom surround-with-mirror-p t
+ "Surround text with mirrored characters.
+If non-nil surrounding text with a character (assuming that character is
+not configured in `surround-pairs-alist') will attempt to surround the
+text with the supplied character and its mirror.
+
+As an example, if `surround-with-mirror-p' is non-nil and the user
+attempts to surround the word “foo” with the character “«” the result
+would be “«foo»”.
+
+Note that unlike `surround-with-paired-bracket-p', because there is no
+concept of an “opening” or “closing” bracket — because this option
+doesn't work in terms of brackets — ordering matters. This means that
+surrounding “Ελλάδα” with “«” will result in “«Ελλάδα»” while
+surrounding “Österreich” with “»” will result in “»Österreich«”.
+
+In more technical terms this function surrounds text with both the
+provided character and the characters corresponding Bidi_Mirroring_Glyph
+Unicode property."
+ :type 'boolean
+ :package-version '(surround . "1.0.0")
+ :group 'surround)
+
+(defvar surround-pairs-alist '((emacs-lisp-mode
+ . ((?` ("`" . "'")))))
+ "TODO")
+
+(defun surround--get-pair-from-alist (char)
+ (declare (ftype (function (char) (cons string string)))
+ (side-effect-free t))
+ (catch 'surround--break
+ (let ((char-as-string (char-to-string char)))
+ (dolist (pair surround-pairs-alist)
+ (let ((mode-or-t (car pair))
+ (pairs (cdr pair)))
+ (when (or (derived-mode-p mode-or-t)
+ (eq t mode-or-t))
+ (dolist (pair pairs)
+ (let ((open-or-trigger (car pair))
+ (closing-or-pair (cdr pair)))
+ (if (numberp open-or-trigger) ; Implies trigger
+ (when (= char open-or-trigger)
+ (throw 'surround--break (car closing-or-pair)))
+ (when (string= char-as-string open-or-trigger)
+ (throw 'surround--break pair)))))))))))
+
+(defun surround--get-pair (char)
+ (declare (ftype (function (char) (cons string string)))
+ (side-effect-free t))
+ (or (surround--get-pair-from-alist char)
+ (let ((char (char-to-string char))
+ (other (char-to-string
+ (or (when surround-with-paired-bracket-p
+ (get-char-code-property char 'paired-bracket))
+ (when surround-with-mirror-p
+ (get-char-code-property char 'mirroring))
+ char)))
+ (bracket-type (get-char-code-property char 'bracket-type)))
+ (pcase bracket-type
+ ((or 'c 'n) (cons other char))
+ ('o (cons char other))))))
+
+(defun suprround--region (pair beginning end)
+ (save-excursion
+ (goto-char beginning)
+ (insert (car pair))
+ (goto-char end)
+ (insert (cdr pair))))
+
+(defun surround-region (char)
+ (interactive
+ (list (read-char-from-minibuffer
+ (format-prompt "Surround with" nil))))
+ (when-let ((pair (surround--get-pair char)))
+ (dolist (bounds (cl-loop for (beginning . end) in (region-bounds)
+ collect (cons (set-marker (make-marker) beginning)
+ (set-marker (make-marker) end))))
+ (surround--region pair (car bounds) (cdr bounds)))))
+
+(defun surround-padded-region (char)
+ (interactive
+ (list (read-char-from-minibuffer
+ (format-prompt "Surround with" nil))))
+ (when-let ((pair (surround--get-pair char))
+ (pair (cons (concat (car pair) " ")
+ (concat " " (cdr pair)))))
+ (dolist (bounds (cl-loop for (beginning . end) in (region-bounds)
+ collect (cons (set-marker (make-marker) beginning)
+ (set-marker (make-marker) end))))
+ (surround--region pair (car bounds) (cdr bounds)))))
+
+(provide 'surround)
diff --git a/.config/emacs/templates b/.config/emacs/templates
index edd8461..5d14264 100644
--- a/.config/emacs/templates
+++ b/.config/emacs/templates
@@ -19,3 +19,12 @@ c-mode c-ts-mode c++-mode c++-ts-mode
n
"#define " header n n r n n
"#endif /* !" header " */")
+
+emacs-lisp-mode
+
+(header
+ ";;; " (file-name-nondirectory (or (buffer-file-name)
+ (buffer-name)))
+ " --- " p " -*- lexical-binding: t; -*-" n n q)
+
+(section "\f" n ";;; " p n n q)