summaryrefslogtreecommitdiff
path: root/.config
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2024-09-08 12:06:50 +0200
committerThomas Voss <mail@thomasvoss.com> 2024-09-08 12:07:32 +0200
commit98cbdb2271e93cf7b45bf5a89106f4353ca9bdfc (patch)
tree339b83b31351e515cedcd834c29b0ba8d5e2d049 /.config
parenta793342e47d2e282fe3ae4f554f7c6cefadfcffe (diff)
emacs: Add the new configuration
Diffstat (limited to '.config')
-rw-r--r--.config/emacs/early-init.el64
-rw-r--r--.config/emacs/init.el691
-rw-r--r--.config/emacs/mango-theme.el159
3 files changed, 914 insertions, 0 deletions
diff --git a/.config/emacs/early-init.el b/.config/emacs/early-init.el
new file mode 100644
index 0000000..b74ba18
--- /dev/null
+++ b/.config/emacs/early-init.el
@@ -0,0 +1,64 @@
+;; -*- lexical-binding: t; -*-
+
+(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
+ (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
+ (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
+ (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))
+
+(setq 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))))
+
+(when (featurep 'native-compile)
+ (startup-redirect-eln-cache
+ (expand-file-name (expand-file-name "eln/" x-cache-directory))))
+
+;; Don’t call the garbage collector during initialization
+(setq gc-cons-threshold most-positive-fixnum)
+(add-hook 'after-init-hook
+ (lambda () (setq gc-cons-threshold (* 512 1-MiB))))
+
+(setq 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)))
+
+(setq package-enable-at-startup nil)
diff --git a/.config/emacs/init.el b/.config/emacs/init.el
new file mode 100644
index 0000000..42c100c
--- /dev/null
+++ b/.config/emacs/init.el
@@ -0,0 +1,691 @@
+;; -*- lexical-binding: t; -*-
+
+ ;;; Preamble
+(setq user-full-name "Thomas Voss")
+(setq user-mail-address "mail@thomasvoss.com")
+
+(when (< emacs-major-version 29)
+ (error "Emacs 29 or newer is required"))
+
+(when (featurep 'native-compile)
+ (setq
+ native-comp-async-report-warnings-errors nil
+ native-comp-verbose 0
+ native-comp-debug 0
+ native-comp-jit-compilation t))
+
+(require 'package)
+(custom-set-variables
+ '(package-archives '(("gnu" . "http://elpa.gnu.org/packages/")
+ ("nongnu" . "https://elpa.nongnu.org/nongnu/")
+ ("melpa" . "http://melpa.org/packages/")))
+ '(package-user-dir (expand-file-name "pkg" x-data-directory)))
+(package-initialize)
+
+(unless (package-installed-p 'use-package)
+ (package-refresh-contents)
+ (package-install 'use-package))
+
+(eval-and-compile
+ (custom-set-variables
+ '(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)
+ "Get the hook corresponding to MODE."
+ (declare (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))
+ (intern (concat
+ (string-remove-suffix "-mode" (symbol-name mode))
+ "-ts-mode")))
+
+(defun x-do-and-center (function &rest arguments)
+ "Call FUNCTION with ARGUMENTS and then center the screen."
+ (apply function arguments)
+ (recenter))
+
+ ;;; Rational Defaults
+(prefer-coding-system 'utf-8)
+(customize-set-variable
+ 'save-interprogram-paste-before-kill t)
+
+(savehist-mode)
+(global-hl-line-mode)
+
+(fset #'yes-or-no-p #'y-or-n-p)
+(dolist (mode #'(blink-cursor-mode
+ menu-bar-mode
+ scroll-bar-mode
+ show-paren-mode
+ tool-bar-mode
+ tooltip-mode))
+ (apply mode '(-1)))
+
+(custom-set-variables
+ '(large-file-warning-threshold nil)
+ '(vc-follow-symlinks t)
+ '(ad-redefinition-action 'accept))
+
+(custom-set-variables
+ '(mouse-wheel-scroll-amount '(1 ((shift) . 1)))
+ '(mouse-wheel-progressive-speed nil)
+ '(mouse-wheel-follow-mouse t)
+ '(scroll-step 1))
+(pixel-scroll-precision-mode)
+
+(customize-set-value 'show-paren-delay 0)
+(dolist (hook '(conf-mode-hook prog-mode-hook))
+ (add-hook hook #'show-paren-local-mode))
+
+(customize-set-value
+ 'read-extended-command-predicate
+ #'command-completion-default-include-p)
+
+(setq-default display-line-numbers 'relative)
+(line-number-mode)
+(column-number-mode)
+
+;; Backup settings
+(custom-set-variables
+ '(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)
+(customize-set-variable
+ 'global-auto-revert-non-file-buffers t)
+(global-auto-revert-mode)
+
+(customize-set-variable
+ '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
+ (("C-c h f" . #'helpful-callable)
+ ("C-c h v" . #'helpful-variable)
+ ("C-c h k" . #'helpful-key)
+ ("C-c h o" . #'helpful-symbol)
+ ("C-c h p" . #'helpful-at-point)))
+
+ ;;; Vim Emulation
+(use-package evil
+ :bind
+ (:map evil-normal-state-map
+ ("g a" . #'x-evil-align-regexp)
+ ("g c" . #'x-evil-comment-or-uncomment-region)
+ ("g s" . #'x-evil-sort-lines)
+ ("C-h" . #'evil-window-left)
+ ("C-j" . #'evil-window-down)
+ ("C-k" . #'evil-window-up)
+ ("C-l" . #'evil-window-right))
+ :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-respect-visual-line-mode 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-set-leader nil (kbd "SPC"))
+ (evil-set-leader nil (kbd ",") 'localleader)
+ (dolist (mode '(normal visual))
+ (evil-global-set-key mode (kbd "C-u") (λi (x-do-and-center #'evil-scroll-up 0)))
+ (evil-global-set-key mode (kbd "C-d") (λi (x-do-and-center #'evil-scroll-down 0)))
+ (evil-global-set-key mode (kbd "C-v") #'evil-visual-line)
+ (evil-global-set-key mode (kbd "M-v") #'evil-visual-block))
+
+ (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 "Align regexp: "))
+ (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)))
+
+(defun x-evil-surround-mode-if-evil-mode ()
+ (global-evil-surround-mode (unless (evil-mode) -1)))
+
+(use-package evil-surround
+ :after evil
+ :hook (evil-mode . x-evil-surround-mode-if-evil-mode)
+ :init
+ (x-evil-surround-mode-if-evil-mode)
+ ;; (global-evil-surround-mode)
+ :config
+ (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))))
+
+ (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" "“" "“" "”")
+
+ (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 'common-lisp-mode 'emacs-lisp-mode)
+ (cons (format "(%s " list-name) ")")
+ (cons (format "%s(" (or list-name "")) ")"))))
+
+ (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 "")) "]")))
+
+ (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))
+
+ ;;; Force Spaces For Alighment
+(defun x-align-with-spaces (function &rest arguments)
+ "Advice to force a given function to align using spaces instead of
+tabs, regardless of the value of ‘indent-tabs-mode’."
+ (let (indent-tabs-mode)
+ (apply function arguments)))
+
+(dolist (f #'(align-regexp c-backslash-region))
+ (advice-add f :around #'x-align-with-spaces))
+
+ ;;; Minibuffer Improvements
+(use-package vertico
+ :bind
+ (:map vertico-map
+ ("C-j" . vertico-next)
+ ("C-k" . vertico-previous)
+ ("C-l" . vertico-insert)
+ :map minibuffer-local-map
+ ("C-h" . x-minibuffer-backward-kill))
+ :custom
+ (vertico-cycle t)
+ :init
+ (vertico-mode)
+ :config
+ (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))))
+
+(use-package marginalia
+ :after vertico
+ :init
+ (marginalia-mode))
+
+(use-package orderless
+ :custom
+ (completion-styles '(orderless basic))
+ (orderless-matching-styles '(orderless-prefixes))
+ (completion-category-overrides '((file (styles basic partial-completion)))))
+
+ ;;; Completions
+;; Disable corfu for now (it’s causing Emacs to crash)
+;; (use-package corfu
+;; :hook ((prog-mode . corfu-mode))
+;; :custom
+;; (corfu-auto t)
+;; (corfu-cycle t)
+;; (corfu-auto-prefix 1)
+;; (corfu-auto-delay 0))
+(defun x-company-require-prefix (candidates)
+ "Transformer for ‘company-mode’ that requires that all candidates begin with
+‘company-prefix’."
+ (seq-filter (lambda (s) (string-prefix-p company-prefix s)) candidates))
+
+(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))))
+
+(defun x-company-next-candidate ()
+ "Select the next available candidate, taking into account if the candidate
+list is flipped or not."
+ (interactive)
+ (x-company-select-candidate #'<))
+
+(defun x-company-previous-candidate ()
+ "Select the previous available candidate, taking into account if the candidate
+list is flipped or not."
+ (interactive)
+ (x-company-select-candidate #'>))
+
+(use-package company
+ :bind (:map company-active-map
+ ("C-j" . #'x-company-next-candidate)
+ ("C-k" . #'x-company-previous-candidate))
+ :hook ((conf-mode prog-mode) . company-mode)
+ :custom
+ (company-minimum-prefix-length 1)
+ (company-idle-delay (lambda () (unless (company-in-string-or-comment) 0)))
+ (company-selection-wrap-around t)
+ (company-tooltip-align-annotations t)
+ (company-tooltip-flip-when-above t)
+ (company-frontends '(company-pseudo-tooltip-unless-just-one-frontend
+ company-preview-frontend
+ company-echo-metadata-frontend))
+ (company-transformers '(x-company-require-prefix
+ company-sort-by-backend-importance)))
+
+ ;;; Increment- and Decrement Numbers
+(defun x-increment-number-at-point (&optional arg)
+ "Increment the number at point by ARG or 1 if ARG is nil. If called
+interactively, the universal argument can be used to specify ARG. If
+the number at point has leading zeros then the width of the number is
+preserved."
+ (interactive "*p")
+ (save-excursion
+ (save-match-data
+ (skip-chars-backward "0123456789")
+ (when (eq (char-before (point)) ?-)
+ (goto-char (1- (point))))
+ (when (re-search-forward "-?\\([0-9]+\\)" nil 'noerror)
+ (let ((answer (+ (string-to-number (match-string 0) 10)
+ (or arg 1)))
+ (width (length (match-string 1))))
+ (replace-match
+ (format
+ (concat "%0" (int-to-string (if (< answer 0) (1+ width) width)) "d")
+ answer)))))))
+
+(defun x-decrement-number-at-point (&optional arg)
+ "The same as ‘x-increment-number-at-point’, but ARG is negated."
+ (interactive "*p")
+ (x-increment-number-at-point (- (or arg 1))))
+
+(keymap-global-set "C-c a" #'x-increment-number-at-point)
+(keymap-global-set "C-c x" #'x-decrement-number-at-point)
+
+ ;;; Indentation Settings
+(setq-default
+ tab-width 4
+ indent-tabs-mode t)
+(customize-set-variable 'evil-shift-width
+ (default-value 'tab-width))
+
+(defvar x-indentation-settings
+ '((c-mode :extra-vars (c-basic-offset))
+ (css-mode :extra-vars (css-indent-offset))
+ (emacs-lisp-mode :spaces t)
+ (graphviz-dot-mode :extra-vars (graphviz-dot-indent-width))
+ (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)))
+ "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))))
+ (mode-hook (intern (concat (symbol-name mode) "-hook")))
+ (mode-ts (intern (concat
+ (string-remove-suffix
+ "-mode" (symbol-name mode))
+ "-ts-mode"))))
+ (dolist (hook (list (x-mode-to-hook mode)
+ (x-mode-to-hook (x-mode-to-ts-mode mode))))
+ (add-hook hook callback 95)))))
+
+(x-set-indentation-settings)
+
+ ;;; Git Integration
+(use-package magit
+ :bind ("C-c g" . magit-status)
+ :custom
+ (magit-display-buffer-function
+ #'magit-display-buffer-same-window-except-diff-v1))
+
+(use-package magit-todos
+ :after magit
+ :init (magit-todos-mode))
+
+(defun x-magit-status ()
+ (interactive)
+ (thread-last
+ (project-current t)
+ (project-root)
+ (magit-status)))
+
+(require 'project)
+(add-to-list 'project-switch-commands '(x-magit-status "Git status" ?g))
+
+ ;;; Tree-Sitter
+(when (treesit-available-p)
+ (setq treesit-language-source-alist
+ '((cpp "https://github.com/tree-sitter/tree-sitter-cpp")
+ (elisp "https://github.com/Wilfred/tree-sitter-elisp")
+ (gomod "https://github.com/camdencheek/tree-sitter-go-mod")
+ (html "https://github.com/tree-sitter/tree-sitter-html")))
+
+ (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)
+ (dolist (spec treesit-language-source-alist)
+ (treesit-install-language-grammar (car spec))))
+
+ (thread-last
+ (mapcar #'car treesit-language-source-alist)
+ (seq-remove #'treesit-language-available-p)
+ (mapc #'treesit-install-language-grammar)))
+
+ ;;; 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))
+ :bind
+ (:map eglot-mode-map
+ ("C-c l a" . #'eglot-code-actions)
+ ("C-c l r" . #'eglot-rename))
+ :init
+ (fset #'jsonrpc--log-event #'ignore)
+ :custom
+ (eglot-events-buffer 0)
+ (eglot-extend-to-xref t)
+ :config
+ (setq eglot-managed-mode-hook
+ (list (λ (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))
+
+ ;;; 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."
+ (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.
+ (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)
+ (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
+ 'depth 'local)
+ (add-hook 'after-save-hook #'x-remove-auto-directory-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."
+ (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)
+ (y-or-n-p (format "Also delete directory ‘%s’?"
+ (directory-file-name dir-to-delete))))
+ (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)
+
+ ;;; User Interface Themeing
+(load-theme 'mango 'no-confirm)
+(set-fringe-style (cons 32 32))
+
+(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 160)
+ "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.")
+
+(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))
+
+ ;;; C-Style
+(defun x-c-defun-open-safe (_syntax _position)
+ (if (c-cpp-define-name)
+ '(after)
+ '(before after)))
+
+(defun x-c (_syn _pos)
+ (message "OMG")
+ '(before))
+
+(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-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")
+
+ ;;; Emacs Calculator
+(setq calc-display-trail nil)
+
+ ;;; Emacs Tetris
+(defun x-tetris-rotate-mirror ()
+ (interactive)
+ (tetris-rotate-next)
+ (tetris-rotate-next))
+
+(use-package tetris
+ :hook (tetris-mode . (lambda () (evil-local-mode -1)))
+ :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" . #'x-tetris-rotate-mirror)
+ ("SPC" . #'tetris-move-bottom)))
+(put 'narrow-to-page 'disabled nil)
diff --git a/.config/emacs/mango-theme.el b/.config/emacs/mango-theme.el
new file mode 100644
index 0000000..d947062
--- /dev/null
+++ b/.config/emacs/mango-theme.el
@@ -0,0 +1,159 @@
+;;; mango-theme.el --- Just your average dark theme -*- lexical-binding: t; -*-
+
+;; Copyright © 2023–2024 Thomas Voss
+
+;; Author: Thomas Voss <mail@thomasvoss.com>
+;; Maintainer: Thomas Voss <mail@thomasvoss.com>
+;; URL: https://git.thomasvoss.com/dotfiles
+
+;;; License:
+
+;; Permission to use, copy, modify, and/or distribute this software for any
+;; purpose with or without fee is hereby granted.
+;;
+;; The software is provided ‘as is’ and the author disclaims all warranties with
+;; regard to this software including all implied warranties of merchantability
+;; and fitness. In no event shall the author be liable for any special, direct,
+;; indirect, or consequential damages or any damages whatsoever resulting from
+;; loss of use, data or profits, whether in an action of contract, negligence or
+;; other tortious action, arising out of or in connection with the use or
+;; performance of this software.
+
+;;; Commentary:
+
+;; TODO
+
+;;; Code:
+
+(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?")
+
+(defun mango-theme--get-color (name)
+ "Get the RGB value of the color NAME from ‘mango-theme-palette’"
+ (cadr (assq name mango-theme-palette)))
+
+(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
+graphically, so I shouldn’t need to have multiple specs per face.
+
+\(fn SPEC...)"
+ (declare (indent 0))
+ (let ((ret '('mango custom-theme-set-faces)))
+ (dolist (spec body)
+ (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-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))
+ (fringe
+ :inherit default)
+
+ ;; Lines
+ (hl-line
+ :background ,(mango-theme--get-color 'background-faint))
+ (region
+ :background ,(mango-theme--get-color 'middleground))
+ (header-line
+ :background ,(mango-theme--get-color 'middleground))
+ (mode-line
+ :inherit header-line)
+ (mode-line-inactive
+ :background ,(mango-theme--get-color 'background-cool)
+ :weight light)
+
+ ;; Line Numbers
+ (line-number
+ :foreground ,(mango-theme--get-color 'background-faint)
+ :background ,(mango-theme--get-color 'background))
+ (line-number-current-line
+ :foreground ,(mango-theme--get-color 'orange)
+ :background ,(mango-theme--get-color 'background)
+ :weight bold)
+
+ ;; Documentation
+ (font-lock-comment-face
+ :foreground ,(mango-theme--get-color 'disabled))
+ (font-lock-doc-face
+ :inherit font-lock-comment-face)
+
+ ;; Core Language
+ (font-lock-keyword-face
+ :foreground ,(mango-theme--get-color 'violet))
+ (font-lock-type-face
+ :foreground ,(mango-theme--get-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))
+ (font-lock-preprocessor-face
+ :foreground ,(mango-theme--get-color 'pink)
+ :weight bold)
+
+ ;; Variables
+ (font-lock-variable-name-face
+ :foreground ,(mango-theme--get-color 'pale-azure))
+ (font-lock-constant-face
+ :inherit font-lock-variable-name-face
+ :weight bold)
+
+ ;; Org Mode
+ (org-code
+ :foreground ,(mango-theme--get-color 'orange))
+ (org-verbatim
+ :foreground ,(mango-theme--get-color 'lime))
+ (org-block
+ :background ,(mango-theme--get-color 'background-cool))
+ (org-hide
+ :foreground ,(mango-theme--get-color 'background))
+ (org-quote
+ :inherit org-block
+ :slant italic)
+
+ ;; Info Page
+ (Info-quoted
+ :inherit default)
+
+ ;; Magit
+ (magit-diff-hunk-heading
+ :background ,(mango-theme--get-color 'background-cool))
+ (magit-diff-hunk-heading-highlight
+ :background ,(mango-theme--get-color 'middleground))
+ (magit-diff-context-highlight
+ :inherit hl-line)
+ (magit-section-highlight
+ :inherit hl-line)
+
+ (git-commit-summary
+ :foreground ,(mango-theme--get-color 'khaki))
+ (git-commit-overlong-summary
+ :foreground ,(mango-theme--get-color 'foreground)
+ :background ,(mango-theme--get-color 'spanish-red)
+ :weight bold)
+
+ ;; Vertico
+ (vertico-current
+ :inherit hl-line))