summaryrefslogtreecommitdiff
path: root/.config/emacs/modules
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2024-10-16 22:04:33 +0200
committerThomas Voss <mail@thomasvoss.com> 2024-10-16 22:04:33 +0200
commit0ee7fa9c382ae30295f0b8d88457f7856c7ff800 (patch)
tree6b5a0cf01fa0bfa4d01b0134b268f5d993055c0b /.config/emacs/modules
parentd452ae1347b3711bc0a7ac80cfa2c37d9d63836e (diff)
emacs: Overhaul configuration completely
Diffstat (limited to '.config/emacs/modules')
-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
14 files changed, 1155 insertions, 0 deletions
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)