diff options
author | Thomas Voss <mail@thomasvoss.com> | 2024-10-16 22:04:33 +0200 |
---|---|---|
committer | Thomas Voss <mail@thomasvoss.com> | 2024-10-16 22:04:33 +0200 |
commit | 0ee7fa9c382ae30295f0b8d88457f7856c7ff800 (patch) | |
tree | 6b5a0cf01fa0bfa4d01b0134b268f5d993055c0b /.config/emacs | |
parent | d452ae1347b3711bc0a7ac80cfa2c37d9d63836e (diff) |
emacs: Overhaul configuration completely
Diffstat (limited to '.config/emacs')
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) |