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/modules | |
parent | d452ae1347b3711bc0a7ac80cfa2c37d9d63836e (diff) |
emacs: Overhaul configuration completely
Diffstat (limited to '.config/emacs/modules')
-rw-r--r-- | .config/emacs/modules/mm-abbrev.el | 86 | ||||
-rw-r--r-- | .config/emacs/modules/mm-calc.el | 25 | ||||
-rw-r--r-- | .config/emacs/modules/mm-completion.el | 58 | ||||
-rw-r--r-- | .config/emacs/modules/mm-darwin.el | 22 | ||||
-rw-r--r-- | .config/emacs/modules/mm-dired.el | 9 | ||||
-rw-r--r-- | .config/emacs/modules/mm-documentation.el | 48 | ||||
-rw-r--r-- | .config/emacs/modules/mm-editing.el | 187 | ||||
-rw-r--r-- | .config/emacs/modules/mm-keybindings.el | 112 | ||||
-rw-r--r-- | .config/emacs/modules/mm-lsp.el | 41 | ||||
-rw-r--r-- | .config/emacs/modules/mm-modeline.el | 115 | ||||
-rw-r--r-- | .config/emacs/modules/mm-projects.el | 70 | ||||
-rw-r--r-- | .config/emacs/modules/mm-tetris.el | 20 | ||||
-rw-r--r-- | .config/emacs/modules/mm-theme.el | 222 | ||||
-rw-r--r-- | .config/emacs/modules/mm-treesit.el | 140 |
14 files changed, 1155 insertions, 0 deletions
diff --git a/.config/emacs/modules/mm-abbrev.el b/.config/emacs/modules/mm-abbrev.el new file mode 100644 index 0000000..29ad72e --- /dev/null +++ b/.config/emacs/modules/mm-abbrev.el @@ -0,0 +1,86 @@ +;;; mm-abbrev.el --- Emacs abbreviations and templates -*- lexical-binding: t; -*- + +;;; Helpers + +(defmacro mm-define-abbreviations (table &rest definitions) + "Define abbrevations for an abbreviation TABLE. +Expand abbrev DEFINITIONS for the given TABLE. DEFINITIONS are a +sequence of either string pairs mapping an abbreviation to its +expansion, or a string and symbol pair mapping an abbreviation to a +function." + (declare (indent 1)) + (unless (cl-evenp (length definitions)) + (user-error "expected an even-number of elements in DEFINITIONS")) + (macroexp-progn + (cl-loop for (abbrev expansion) in (seq-partition definitions 2) + if (stringp expansion) + collect (list #'define-abbrev table abbrev expansion) + else + collect (list #'define-abbrev table abbrev "" expansion)))) + + +;;; Abbreviation Configuration + +(use-package abbrev + :init + (setq-default abbrev-mode t) + :custom + (abbrev-file-name (expand-file-name "abbev-defs" mm-data-directory)) + (save-abbrevs 'silently)) + + +;;; Abbreviation Definitions + +(defvar mm-c-mode-abbrev-table (make-abbrev-table) + "Abbreviations shared between `c-mode', `c++-mode', `c-ts-mode', and +`c++-ts-mode'.") + +(mm-define-abbreviations mm-c-mode-abbrev-table + "flf" "flockfile" + "fpf" "fprintf" + "fuf" "funlockfile" + "pf" "printf" + "se" "stderr" + "si" "stdin" + "so" "stdout") + +(with-eval-after-load 'cc-mode + (setq c-mode-abbrev-table (copy-abbrev-table mm-c-mode-abbrev-table) + c++-mode-abbrev-table (copy-abbrev-table mm-c-mode-abbrev-table))) +(with-eval-after-load 'c-ts-mode + (setq c-ts-mode-abbrev-table (copy-abbrev-table mm-c-mode-abbrev-table) + c++-ts-mode-abbrev-table (copy-abbrev-table mm-c-mode-abbrev-table))) + +(mm-define-abbreviations emacs-lisp-mode-abbrev-table + "ald" ";;;###autoload" + "gc" "goto-char" + "ins" "insert" + "nrt" "narrow-to-region" + "pmn" "point-min" + "pmx" "point-max" + "pnt" "point" + "rap" "region-active-p" + "rb" "region-beginning" + "re" "region-end" + "se" "save-excursion" + "sme" "save-mark-and-excursion" + "sr" "save-restriction") + + +;;; Template Configuration + +(use-package tempel + :ensure t + :demand t + :pin gnu + :bind (:map tempel-map + ("TAB" . tempel-next) + ("S-TAB" . tempel-previous)) + :custom + (tempel-trigger-prefix ",") + :init + (setopt tempel-path (expand-file-name "templates" mm-config-directory)) + (add-hook 'completion-at-point-functions #'tempel-complete -10) + (add-to-list 'auto-mode-alist (cons tempel-path #'lisp-data-mode))) + +(provide 'mm-abbrev) diff --git a/.config/emacs/modules/mm-calc.el b/.config/emacs/modules/mm-calc.el new file mode 100644 index 0000000..2a9a74b --- /dev/null +++ b/.config/emacs/modules/mm-calc.el @@ -0,0 +1,25 @@ +;;; mm-calc.el --- Emacs configurations for ‘calc-mode’ -*- lexical-binding: t; -*- + +;; TODO: Swap more than 2 elements? +(defun mm-calc-swap () + "Swap the top two elements on the stack." + (declare (modes calc-mode)) + (interactive) + (calc-over 2) + (calc-truncate-up 2) + (calc-pop 1) + (calc-truncate-down 2)) + +(use-package calc + :bind (:map calc-mode-map + ("C-c x" . #'mm-calc-swap)) + ;; TODO: Can this be done in :custom? + :init + (setopt + calc-display-trail nil + calc-group-digits t + ;; Optimize for Europeans + calc-point-char "," + calc-group-char ".")) + +(provide 'mm-calc) diff --git a/.config/emacs/modules/mm-completion.el b/.config/emacs/modules/mm-completion.el new file mode 100644 index 0000000..9ea7746 --- /dev/null +++ b/.config/emacs/modules/mm-completion.el @@ -0,0 +1,58 @@ +;;; mm-completion.el --- Configuration for Emacs completion -*- lexical-binding: t; -*- + + +;;; Vertical Completions + +(use-package vertico + :ensure t + :custom + (vertico-cycle t) + :init + (vertico-mode) + :config + ;; Highlight the current line + (require 'hl-line)) + + +;;; Annotate Completions + +(use-package marginalia + :ensure t + :custom + (marginalia-field-width 50) + :init + (marginalia-mode)) + + +;;; Orderless Completion Style + +;; TODO: Make sure this doesn’t suck +(use-package orderless + :ensure t + :custom + (completion-styles '(orderless basic)) + (orderless-matching-styles '(orderless-prefixes)) + (completion-category-overrides '((file (styles basic partial-completion))))) + + +;;; Completion Popups + +(use-package corfu + :ensure t + :hook ((prog-mode . corfu-mode)) + :bind (:map corfu-map + ("C-<return>" . newline)) + :custom + (corfu-auto t) + (corfu-cycle t) + (corfu-auto-prefix 1) + (corfu-auto-delay 0)) + + +;;; Save Minibuffer History + +(use-package savehist-mode + :init + (savehist-mode)) + +(provide 'mm-completion) diff --git a/.config/emacs/modules/mm-darwin.el b/.config/emacs/modules/mm-darwin.el new file mode 100644 index 0000000..79df610 --- /dev/null +++ b/.config/emacs/modules/mm-darwin.el @@ -0,0 +1,22 @@ +;;; mm-darwin.el --- MacOS Configuration -*- lexical-binding: t; -*- + +(unless (featurep 'ns) + (error "'NS not available. Something has gone horribly wrong.")) + + +;;; Launch Emacs Properly + +(defun mm-ns-raise-emacs () + (ns-do-applescript "tell application \"Emacs\" to activate")) + +(add-hook + 'after-make-frame-functions + (defun mm-ns-raise-emacs-with-frame (frame) + (when (display-graphic-p) + (with-selected-frame frame + (mm-ns-raise-emacs))))) + +(when (display-graphic-p) + (mm-ns-raise-emacs)) + +(provide 'mm-darwin) diff --git a/.config/emacs/modules/mm-dired.el b/.config/emacs/modules/mm-dired.el new file mode 100644 index 0000000..ada73a3 --- /dev/null +++ b/.config/emacs/modules/mm-dired.el @@ -0,0 +1,9 @@ +;;; mm-dired.el --- Configure the directory editor -*- lexical-binding: t; -*- + +(use-package dired + :hook ((dired-mode . dired-omit-mode) + (dired-mode . dired-hide-details-mode)) + :bind (:map dired-mode-map + ("f" . find-file))) + +(provide 'mm-dired) diff --git a/.config/emacs/modules/mm-documentation.el b/.config/emacs/modules/mm-documentation.el new file mode 100644 index 0000000..473c766 --- /dev/null +++ b/.config/emacs/modules/mm-documentation.el @@ -0,0 +1,48 @@ +;;; mm-documentation.el --- Configuration related to documentation -*- lexical-binding: t; -*- + + +;;; Display Available Keybindings + +(use-package which-key + :demand t + :config + (which-key-mode) + :custom + (which-key-dont-use-unicode nil) + (which-key-ellipsis "…") + (wihch-key-idle-delay .5)) + + +;;; Enhance Describe Commands + +(use-package helpful + :ensure t + :bind (([remap describe-command] . helpful-command) + ([remap describe-function] . helpful-callable) + ([remap describe-key] . helpful-key) + ([remap describe-symbol] . helpful-symbol) + ([remap describe-variable] . helpful-variable) + (("C-h C-p" . helpful-at-point)))) + + +;;; Open Manpage for Symbol + +(defun mm-documentation-man-at-point () + "Open a UNIX manual page for the symbol at point." + (declare (modes (c-mode c++-mode c-ts-mode c++-ts-mode))) + (interactive) + (if-let ((symbol + (pcase major-mode + ((or 'c-mode 'c++-mode) + (thing-at-point 'symbol :no-properties)) + ((or 'c-ts-mode 'c++-ts-mode) + (when-let ((node (treesit-thing-at-point "identifier" 'nested))) + (treesit-node-text node :no-properties)))))) + (man symbol) + (message "No symbol at point."))) + +(dolist (mode '(c-mode c++-mode c-ts-mode c++-ts-mode)) + (with-eval-after-load mode + (require 'man))) + +(provide 'mm-documentation) diff --git a/.config/emacs/modules/mm-editing.el b/.config/emacs/modules/mm-editing.el new file mode 100644 index 0000000..fb1475d --- /dev/null +++ b/.config/emacs/modules/mm-editing.el @@ -0,0 +1,187 @@ +;;; mm-editing.el --- Text editing configuation -*- lexical-binding: t; -*- + +;;; Delete Region When Typing + +(use-package delsel + :init + (delete-selection-mode)) + + +;;; Force Spaces For Alignment + +(defun mm-editing-force-space-indentation (function &rest arguments) + "Call FUNCTION with ARGUMENTS in an environment in which +`indent-tabs-mode' is nil." + (let (indent-tabs-mode) + (apply function arguments))) + +(dolist (command #'(align c-backslash-region comment-dwim)) + (advice-add command :around #'mm-editing-force-space-indentation)) + + +;;; Indentation Settings + +(setq-default + tab-width 4 + indent-tabs-mode t) + +(defvar mm-editing-indentation-settings-alist + '((c-mode . (:extras c-basic-offset)) + (c-ts-mode . (:extras c-ts-mode-indent-offset)) + (css-mode . (:extras css-indent-offset)) + (emacs-lisp-mode . (:width 8 :spaces t)) ; GNU code uses 8-column tabs + (go-ts-mode . (:extras go-ts-mode-indent-offset)) + (go-mod-ts-mode . (:extras go-ts-mode-indent-offset)) + (gsp-ts-mode . (:width 2 :extras gsp-ts-mode-indent-rules)) + (helpful-mode . (:width 8)) ; GNU code uses 8-column tabs + (lisp-data-mode . (:spaces t)) + (lisp-mode . (:spaces t)) + (org-mode . (:spaces t)) + (python-mode . (:extras python-indent-offset)) + (python-ts-mode . (:extras python-indent-offset)) + (sgml-mode . (:extras sgml-basic-offset)) + (sh-mode . (:extras sh-basic-offset)) + (vimscript-ts-mode . (:extras vimscript-ts-mode-indent-level))) + "Alist of indentation settings. +Each pair in this alist is of the form (MODE . SETTINGS) where MODE +specifies the mode for which the given SETTINGS should apply. + +SETTINGS is a plist of one-or-more of the following keys: + + `:spaces' -- If nil force tabs for indentation, if non-nil for spaces + for indentation. If this key is not provided then the + value of `indent-tabs-mode' is used. + `:width' -- Specifies a non-negative number to be used as the tab + width and indentation offset. If this key is not + provided then the default value of `tab-width' is used. + `:extras' -- A list of mode-specific variables which control + indentation settings that need to be set for + configurations to properly be applied.") + +(defun mm-editing-set-indentation-settings () + "Set indentation settings for the current major mode. +The indentation settings are set based on the configured values in +`mm-editing-indentation-settings-alist'." + (let* ((plist (alist-get major-mode mm-editing-indentation-settings-alist)) + (spaces (plist-member plist :spaces)) + (width (plist-member plist :width)) + (extras (plist-member plist :extras))) + (when spaces + (indent-tabs-mode (and (cadr spaces) -1))) + (when width + (setq-local tab-width (cadr width))) + (when extras + (setq extras (cadr extras)) + (when (symbolp extras) + (setq extras (list extras))) + (dolist (extra extras) + (set extra tab-width))))) + +(add-hook 'after-change-major-mode-hook #'mm-editing-set-indentation-settings) + +(defun mm-editing-set-tabsize () + "Set the tabsize for the current buffer. +If the current buffer’s major mode requires setting additional variables, +those should be listed in `mm-editing-indentation-settings'." + (interactive) + (let* ((prompt-default (default-value 'tab-width)) + (prompt (format-prompt "Tabsize" prompt-default)) + (tabsize (mm-as-number (read-string prompt nil nil prompt-default)))) + (setq-local tab-width tabsize) + (when-let* ((plist (alist-get major-mode mm-editing-indentation-settings)) + (extras (plist-get plist :extras))) + (dolist (extra (if (symbolp extras) + (list extras) + extras)) + (set (make-local-variable extra) tabsize))))) + + +;;; Multiple Cursors + +(use-package multiple-cursors + :ensure t + :bind (("C->" . #'mc/mark-next-like-this) + ("C-<" . #'mc/mark-previous-like-this) + ("C-M-<" . #'mc/mark-all-like-this-dwim) + ("C-M->" . #'mc/edit-lines)) + :init + (with-eval-after-load 'multiple-cursors-core + (dolist (command #'(delete-backward-char + delete-forward-char + backward-delete-char + capitalize-dwim + downcase-dwim + upcase-dwim)) + (add-to-list 'mc/cmds-to-run-for-all command)) + (dolist (command #'(helpful-callable + helpful-key + helpful-symbol + helpful-variable)) + (add-to-list 'mc/cmds-to-run-once command)) + (add-to-list 'mc/unsupported-minor-modes #'corfu-mode))) + + +;;; Increment Numbers + +(use-package increment + :bind (("C-c i i" . #'increment-number-at-point) + ("C-c i d" . #'decrement-number-at-point)) + :commands (increment-number-at-point decrement-number-at-point)) + + +;;; Surround With Delimeters + +(defun mm-editing-surround-with-spaces (char) + "Surrounds region or current symbol with a pair defined by CHAR. +This is the same as `surround-insert' except it pads the contents of the +surround with spaces." + (interactive + (list (char-to-string (read-char "Character: ")))) + (let* ((pair (surround--make-pair char)) + (left (car pair)) + (right (cdr pair)) + (bounds (surround--infer-bounds t))) + (save-excursion + (goto-char (cdr bounds)) + (insert " " right) + (goto-char (car bounds)) + (insert left " ")) + (when (eq (car bounds) (point)) + (forward-char)))) + +;; TODO: Implement this manually +(use-package surround + :ensure t + :bind-keymap ("M-'" . surround-keymap) + :bind (:map surround-keymap + ("S" . #'mm-editing-surround-with-spaces)) + :config + (dolist (pair '(("‘" . "’") + ("“" . "”") + ("»" . "«") + ("⟮" . "⟯"))) + (push pair surround-pairs)) + (make-variable-buffer-local 'surround-pairs) + (add-hook 'emacs-lisp-mode-hook + (defun mm-editing-add-elisp-quotes-pair () + (push '("`" . "'") surround-pairs)))) + + +;;; Emmet Mode + +(defun mm-editing-emmet-dwim (arg) + "Do-What-I-Mean Emmet expansion. +If the region is active then the region will be surrounded by an emmet +expansion read from the minibuffer. Otherwise the emmet expression +before point is expanded. When provided a prefix argument the behaviour +is as described by `emmet-expand-line'." + (interactive "P") + (if (region-active-p) + (call-interactively #'emmet-wrap-with-markup) + (emmet-expand-line arg))) + +(use-package emmet-mode + :ensure t + :bind ("C-," . mm-editing-emmet-dwim)) + +(provide 'mm-editing) diff --git a/.config/emacs/modules/mm-keybindings.el b/.config/emacs/modules/mm-keybindings.el new file mode 100644 index 0000000..d01b447 --- /dev/null +++ b/.config/emacs/modules/mm-keybindings.el @@ -0,0 +1,112 @@ +;;; mm-keybindings.el --- Emacs keybindings -*- lexical-binding: t; -*- + +(require 'editing) + +;; The following keys are either unbound and are free to populate, or are +;; bound to functions I don’t care for: +;; ‘C-i’, ‘C-j’, ‘C-o’, ‘C-.’, ‘C-{’, ‘C-}’, ‘C-|’, ‘C-/’, ‘C-\;’, ‘C-:’ + + +;;; Helper Macros + +(defmacro mm-keymap-set (keymap &rest definitions) + (declare (indent 1)) + (unless (cl-evenp (length definitions)) + (user-error "Expected an even-number of elements in DEFINITIONS.")) + `(cl-loop for (from to) on (list ,@definitions) by #'cddr + do (keymap-set ,keymap from to))) + +(defmacro mm-keymap-set-repeating (keymap &rest definitions) + (declare (indent 1)) + (unless (cl-evenp (length definitions)) + (user-error "Expected an even-number of elements in DEFINITIONS.")) + (let ((keymap-gen (gensym "mm-keybindings--repeat-map-"))) + `(progn + (defvar-keymap ,keymap-gen) + (cl-loop for (from to) on (list ,@definitions) by #'cddr + do (progn + (keymap-set ,keymap-gen from to) + (put to 'repeat-map ',keymap-gen)))))) + +(defmacro mm-keymap-remap (keymap &rest commands) + "Define command remappings for a given KEYMAP. +COMMANDS is a sequence of unquoted commands. For each pair of COMMANDS +the first command is remapped to the second command." + (declare (indent 1)) + (unless (cl-evenp (length commands)) + (user-error "Expected an even-number of elements in COMMANDS.")) + (macroexp-progn + (cl-loop for (from to) in (seq-partition commands 2) + collect `(keymap-set + ,keymap + ,(concat "<remap> <" (symbol-name from) ">") + #',to)))) + + +;;; Disable ESC as Meta + +(keymap-global-set "<escape>" #'ignore) + + +;;; Enable Repeat Bindings + +(use-package repeat + :init + (repeat-mode)) + + +;;; Remap Existing Bindings + +(mm-keymap-remap global-map + backward-delete-char-untabify backward-delete-char + kill-ring-save e/kill-ring-save-dwim + + capitalize-word capitalize-dwim + downcase-word downcase-dwim + upcase-word upcase-dwim + + mark-word e/mark-entire-word + mark-sexp e/mark-entire-sexp + + transpose-chars e/transpose-previous-chars + transpose-lines e/transpose-current-and-next-lines) + +(with-eval-after-load 'cc-vars + (setopt c-backspace-function #'backward-delete-char)) + + +;;; Remove Unwanted Bindings + +(keymap-global-unset "C-x C-c" :remove) ; ‘capitalize-region’ +(keymap-global-unset "C-x C-l" :remove) ; ‘downcase-region’ +(keymap-global-unset "C-x C-u" :remove) ; ‘upcase-region’ + + +;;; Bind Commands Globally + +(mm-keymap-set global-map + "<next>" #'e/scroll-up + "<prior>" #'e/scroll-down + "C-<next>" #'forward-page + "C-<prior>" #'backward-page + + "C-." #'repeat + "C-/" #'e/mark-line-dwim + + "C-c d" #'duplicate-dwim + "C-c t a" #'e/align-regexp + "C-c t f" #'fill-paragraph + "C-c t s" #'sort-lines + "C-c j" #'e/join-current-and-next-line + "C-c J" #'join-line) + +(mm-keymap-set-repeating global-map + "j" #'e/join-current-and-next-line + "J" #'join-line) + +(with-eval-after-load 'increment + (mm-keymap-set-repeating global-map + "d" #'decrement-number-at-point + "i" #'increment-number-at-point)) + +(provide 'mm-keybindings) diff --git a/.config/emacs/modules/mm-lsp.el b/.config/emacs/modules/mm-lsp.el new file mode 100644 index 0000000..2e0feac --- /dev/null +++ b/.config/emacs/modules/mm-lsp.el @@ -0,0 +1,41 @@ +;;; mm-lsp.el --- Language Server Protocol configuration -*- lexical-binding: t; -*- + +;;; Configure LSP + +(defun mm-lsp-eglot-no-inlay-hints () + "Disable inlay hints when `eglot' is enabled." + (eglot-inlay-hints-mode -1)) + +(use-package eglot + :hook (((c-mode c-ts-mode + c++-mode c++-ts-mode + go-ts-mode + python-mode python-ts-mode + js-mode js-ts-mode) + . eglot-ensure) + (eglot-managed-mode . mm-lsp-eglot-no-inlay-hints)) + :init + (fset #'jsonrpc--log-event #'ignore) + :custom + (eglot-events-buffer 0) + (eglot-extend-to-xref t) + :config + (add-to-list 'eglot-stay-out-of 'flymake) + (add-to-list 'eglot-server-programs + '((c-mode c-ts-mode c++-mode c++-ts-mode) + . ("clangd" "--header-insertion=never")))) + + +;;; Use Tempel for Snippets + +(defun mm-lsp-eglot-tempel-enable () + "Enable `eglot-tempel-mode'. +If `eglot-tempel-mode' is already enabled this function does nothing." + (unless (default-value eglot-tempel-mode) + (eglot-tempel-mode))) + +(use-package eglot-tempel + :after eglot + :hook (eglot-managed-mode . mm-lsp-eglot-tempel-enable)) + +(provide 'mm-lsp) diff --git a/.config/emacs/modules/mm-modeline.el b/.config/emacs/modules/mm-modeline.el new file mode 100644 index 0000000..cd9af3e --- /dev/null +++ b/.config/emacs/modules/mm-modeline.el @@ -0,0 +1,115 @@ +;;; mm-modeline.el --- Pluggable modeline components -*- lexical-binding: t; -*- + +(defmacro mm-modeline--define-component (name &rest forms) + (declare (indent 1)) + `(progn + (defvar-local ,name '(:eval (or ,(macroexp-progn forms) ""))) + (put ',name 'risky-local-variable t))) + + +;;; Faces + +(defface mm-modeline-narrow-face + '((t :foreground "#C5C8C6" ; From ‘mango-theme’ + :background "dark red" + :box "dark red" + :weight bold)) + "Face for the `mm-modeline-narrow' modeline component.") + + +;;; Support Icons + +(use-package all-the-icons + :ensure t + :init + (defvar mm-all-the-icons-cookie + (expand-file-name ".all-the-icons-installed-p" mm-cache-directory)) + (unless (file-exists-p mm-all-the-icons-cookie) + (all-the-icons-install-fonts) + (make-empty-file mm-all-the-icons-cookie :parents)) + (set-char-table-range char-width-table #xE907 2)) + + +;;; Modeline Components + +(mm-modeline--define-component mm-modeline-readonly + (when buffer-read-only + (propertize " READONLY" 'face 'bold))) + +(mm-modeline--define-component mm-modeline-buffer-name + (propertize "%b" 'face 'font-lock-constant-face)) + +(mm-modeline--define-component mm-modeline-buffer-modified + (when (and (buffer-modified-p) + (buffer-file-name)) + (propertize " (modified)" 'face 'shadow))) + +(mm-modeline--define-component mm-modeline-major-mode-name + (propertize + (thread-last + major-mode + (symbol-name) + (capitalize) + (string-replace "-" " ") + (string-replace "Ts Mode" "Tree-Sitter Mode") + ;; Casing doesn’t work for abbreviations, so fix it manually + (replace-regexp-in-string "\\<\\(M\\)?html\\>" "\\1HTML") + (replace-regexp-in-string "\\<\\(S\\)?css\\>" "\\1CSS") + (replace-regexp-in-string "\\<toml\\>" "TOML") + (replace-regexp-in-string "\\<gsp\\>" "GSP")) + 'face 'bold)) + +(mm-modeline--define-component mm-modeline-major-mode-symbol + (propertize + (cond + ((derived-mode-p 'prog-mode) "λ ") + ((derived-mode-p '(text-mode conf-mode)) "§ ") + ((derived-mode-p 'comint-mode) ">_ ") + (t "")) + 'face 'shadow)) + +(mm-modeline--define-component mm-modeline-narrow + (when (buffer-narrowed-p) + (propertize + " Narrow " + 'face 'mm-modeline-narrow-face))) + +(mm-modeline--define-component mm-modeline-git-branch + (when-let ((branch (car (and (featurep 'vc-git) + (vc-git-branches))))) + (concat + (propertize "\uE907" 'display '(raise 0)) + " " + (propertize branch 'face 'font-lock-constant-face) + " │ "))) + + +;;; Padding Between Left and Right + +(mm-modeline--define-component mm-modeline-left-right-padding + (let ((length (string-width (format-mode-line mm-modeline-right)))) + (propertize " " 'display `(space :align-to (- right ,length))))) + + +;;; Configure Modeline + +(setopt mode-line-format-right-align 'right-margin) + +(setq + mm-modeline-left (list mm-modeline-narrow + mm-modeline-readonly + " " + mm-modeline-buffer-name + mm-modeline-buffer-modified + " │ " + mm-modeline-major-mode-symbol + mm-modeline-major-mode-name + mm-modeline-left-right-padding + mode-line-end-spaces) + mm-modeline-right (list mm-modeline-git-branch + "%l:%c ")) + +(setq-default + mode-line-format + (list mm-modeline-left mm-modeline-left-right-padding mm-modeline-right)) +(provide 'mm-modeline) diff --git a/.config/emacs/modules/mm-projects.el b/.config/emacs/modules/mm-projects.el new file mode 100644 index 0000000..fad56ae --- /dev/null +++ b/.config/emacs/modules/mm-projects.el @@ -0,0 +1,70 @@ +;;; mm-projects.el --- Configuration for project management -*- lexical-binding: t; -*- + +;;; Project Configuration + +(defun mm-projects-project-magit-status () + "Open a Git status buffer for the current project. +This is intended to be called interactively via + `project-switch-commands'." + (interactive) + (thread-last + (project-current t) + (project-root) + (magit-status-setup-buffer))) + +(use-package project + :defer 1 ; Marginal startup performance improvement + :custom + (project-switch-commands '((project-dired "Dired" ?d) + (project-find-file "Find File") + (project-find-regexp "Find Regexp") + (mm-projects-project-magit-status "Git Status" ?s))) + :config + (unless mm-darwin-p + (if-let ((repo-directory (getenv "REPODIR"))) + (mm-with-suppressed-output + (thread-last + (directory-files repo-directory :full "\\`[^.]") + (mapcar (lambda (path) (concat path "/"))) ; Avoid duplicate entries + (mapc #'project-remember-projects-under))) + (warn "The REPODIR environment variable is not set.")))) + + +;;; Git Client + +(use-package magit + :ensure t + :custom + (transient-default-level 7) + (magit-display-buffer-function + #'magit-display-buffer-same-window-except-diff-v1) + :config + (transient-define-suffix mm-projects-magit-push-current-to-all-remotes (args) + "Push the current branch to all remotes." + :if #'magit-get-current-branch + (interactive (list (magit-push-arguments))) + (run-hooks 'magit-credential-hook) + (let ((branch (magit-get-current-branch))) + (dolist (remote (magit-list-remotes)) + (magit-run-git-async + "push" "-v" args remote + (format "refs/heads/%s:refs/heads/%s" branch branch))))) + (transient-append-suffix #'magit-push '(1 -1) + '("a" "all remotes" mm-projects-magit-push-current-to-all-remotes))) + +(use-package magit-todos + :ensure t + :after magit + :hook magit-mode + :custom + (magit-todos-exclude-globs '("vendor/"))) + + +;; Project Compilation + +(use-package compile + :config + (require 'ansi-color) + (add-hook 'compilation-filter-hook #'ansi-color-compilation-filter)) + +(provide 'mm-projects) diff --git a/.config/emacs/modules/mm-tetris.el b/.config/emacs/modules/mm-tetris.el new file mode 100644 index 0000000..06fae58 --- /dev/null +++ b/.config/emacs/modules/mm-tetris.el @@ -0,0 +1,20 @@ +;;; mm-tetris.el --- Emacs configurations for ‘tetris’ -*- lexical-binding: t; -*- + +(defun mm-tetris-rotate-mirror () + "Rotate the current piece by 180°." + (declare (modes tetris-mode)) + (interactive) + (tetris-rotate-next) + (tetris-rotate-next)) + +(use-package tetris + :bind (:map tetris-mode-map + ("a" . tetris-move-left) + ("d" . tetris-move-right) + ("k" . tetris-rotate-next) + (";" . tetris-rotate-prev) + ("l" . tetris-move-down) + ("o" . mm-tetris-rotate-mirror) + ("SPC" . tetris-move-bottom))) + +(provide 'mm-tetris) diff --git a/.config/emacs/modules/mm-theme.el b/.config/emacs/modules/mm-theme.el new file mode 100644 index 0000000..8aa82cd --- /dev/null +++ b/.config/emacs/modules/mm-theme.el @@ -0,0 +1,222 @@ +;;; mm-theme.el --- Emacs theme settings -*- lexical-binding: t; -*- + + +;;; Custom Theme + +(load-theme 'mango :no-confirm) + + +;;; Fonts + +(defvar mm-theme-monospace-font `(,(if mm-darwin-p + "Iosevka Custom" + "Iosevka Smooth") + :weight regular + :height 162) + "The default monospace font. +This is a plist containing a font name, -weight, and -height.") + +(defvar mm-theme-proportional-font '("SF Pro" :weight regular :height 162) + "The default proportional font. +This is a plist containing a font name, -weight, and -height.") + +(defun mm-theme-set-fonts (&optional _frame) + "Set frame font settings. +Sets the frame font settings according to the fonts specified by +`mm-theme-monospace-font' and `mm-theme-proportional-font'. + +This function can be used as a hook in `after-make-frame-functions' and +_FRAME is ignored." + (interactive) + (let* ((mono-family (car mm-theme-monospace-font)) + (mono-props (cdr mm-theme-monospace-font)) + (prop-family (car mm-theme-proportional-font)) + (prop-props (cdr mm-theme-proportional-font)) + (mono-weight (plist-get mono-props :weight)) + (mono-height (plist-get mono-props :height)) + (prop-weight (plist-get prop-props :weight)) + (prop-height (plist-get prop-props :height))) + ;; Some characters in this font are larger than usual + (when (string= mono-family "Iosevka Smooth") + (dolist (rune '(?… ?— ?← ?→ ?⇐ ?⇒ ?⇔)) + (set-char-table-range char-width-table rune 2))) + (set-face-attribute 'default nil + :font mono-family + :weight mono-weight + :height mono-height) + (set-face-attribute 'fixed-pitch nil + :font mono-family + :weight mono-weight + :height mono-height) + (set-face-attribute 'variable-pitch nil + :font prop-family + :weight prop-weight + :height prop-height))) + +(if (daemonp) + (add-hook 'after-make-frame-functions #'mm-theme-set-fonts) + (mm-theme-set-fonts)) + +;; Ligature Settings + +(defvar mm-theme-ligatures-alist + `(((c-mode c-ts-mode c++-mode c++-ts-mode) + . ("->")) + ((c++-mode c++-ts-mode) + . ("::")) + ((js-mode js-ts-mode typescript-ts-mode vue-ts-mode) + . (("=" ,(rx (or ?> (** 1 2 ?=)))) + ("!" ,(rx (** 1 2 ?=))))) + (go-ts-mode + . (":=" "<-")) + ((python-mode python-ts-mode) + . (":=" "->")) + ((mhtml-mode html-mode html-ts-mode vue-ts-mode) + . ("<!--" "-->" "/>")) + (prog-mode + . ("<=" ">=" "==" "!=" "*=" "__"))) + "Ligatures to enable in specific modes. +Elements of this alist are of the form: + + (SPEC . LIGATURES) + +Where LIGATURES is a list of ligatures to enable for the set of modes +described by SPEC. + +SPEC can be either a symbol, or a list of symbols. These symbols should +correspond to modes for which the associated LIGATURES should be enabled. + +A mode may also be specified in multiple entries. To configure +`go-ts-mode' to have its set of ligatures be a super-set of the +ligatures for `c-ts-mode', the following two entries could be added: + + \\='((c-ts-mode go-ts-mode) . (\">=\" \"<=\" \"!=\" \"==\")) + \\='(go-ts-mode . (\":=\"))") + +(defun mm-theme-update-ligatures () + "Update the ligature composition tables. +After running this function you may need to restart `ligature-mode'. + +Also see `mm-theme-ligatures-alist'." + (interactive) + (setopt ligature-composition-table nil) + (cl-loop for (spec . ligatures) in mm-theme-ligatures-alist + do (ligature-set-ligatures spec ligatures))) + +(use-package ligature + :ensure t + :if (and (or mm-darwin-p + (seq-contains-p (split-string system-configuration-features) + "HARFBUZZ")) + (display-graphic-p)) + :commands ligature-mode + :init + ;; Add ‘ligature-mode’ as a hook for all modes configured in + ;; ‘mm-theme-ligatures-alist’ + (thread-last + mm-theme-ligatures-alist + (mapcar #'car) + (flatten-tree) + (seq-uniq) + (mapcar #'mm-mode-to-hook) + (mapc (lambda (mode) (add-hook mode #'ligature-mode)))) + :config + (mm-theme-update-ligatures)) + + +;;; Background Opacity + +(defvar mm-theme-background-opacity 100 + "Opacity of the graphical Emacs frame. +A value of 0 is fully transparent while 100 is fully opaque.") + +(defun mm-theme-background-opacity (opacity) + "Set the current frames' background opacity. +See also the `mm-theme-background-opacity' variable." + (interactive + (list (mm-as-number + (read-string + (format-prompt "Background opacity" + (default-value 'mm-theme-background-opacity)) + nil nil mm-theme-background-opacity)))) + (set-frame-parameter nil 'alpha-background opacity)) + +(add-to-list + 'default-frame-alist (cons 'alpha-background mm-theme-background-opacity)) + + +;;; Divider Between Windows + +(use-package frame + :init + (window-divider-mode)) + + +;;; Pulse Line on Jump + +(use-package pulsar + :ensure t + :demand t + :custom + (pulsar-pulse t) + (pulsar-delay .05) + (pulsar-iterations 10) + :config + (add-to-list 'pulsar-pulse-functions #'jump-to-register) + (add-to-list 'pulsar-pulse-functions #'e/scroll-up) + (add-to-list 'pulsar-pulse-functions #'e/scroll-down) + ;; Integrate with ‘compilation-mode’ + (add-hook 'next-error-hook #'pulsar-pulse-line) + (pulsar-global-mode) + :hook + ((next-error . (pulsar-pulse-line-red + pulsar-recenter-top + pulsar-reveal-entry)) + (minibuffer-setup . pulsar-pulse-line-red)) + :bind + (("C-c h l" . pulsar-highlight-dwim))) + + +;;; Add Padding + +(use-package spacious-padding + :ensure t + :demand t + :config + (spacious-padding-mode)) + + +;;; Pretty Page Boundaries + +(use-package page-break-lines + :ensure t + :demand t + :init + (add-hook + 'change-major-mode-hook + (defun mm-theme--set-page-break-max-width () + (setopt page-break-lines-max-width fill-column))) + (global-page-break-lines-mode) + :config + ;; Since the ‘^L’ character is replaced by a horizontal rule, the + ;; cursor should appear below the horizontal rule. When moving + ;; backwards we need to account for the fact that the cursor is + ;; actually one character ahead of hte page break and adjust + ;; accordingly. + (advice-add + #'forward-page :after + (defun mm-theme--forward-char (&rest _) + (forward-char))) + (advice-add + #'backward-page :before + (defun mm-theme--backward-char (&rest _) + (backward-char)))) + + +;;; More Intuiative UI for Certain Modes + +(use-package line-selection-mode + :hook ((dired-mode . line-selection-mode) + (ibuffer-mode . line-selection-mode))) + +(provide 'mm-theme) diff --git a/.config/emacs/modules/mm-treesit.el b/.config/emacs/modules/mm-treesit.el new file mode 100644 index 0000000..75e679f --- /dev/null +++ b/.config/emacs/modules/mm-treesit.el @@ -0,0 +1,140 @@ +;;; mm-treesit.el --- Tree-Sitter configuration -*- lexical-binding: t; -*- + +(unless (treesit-available-p) + (error "Tree-Sitter is not available.")) + + +;;; Tree-Sitter Variables + +(defvar mm-treesit-language-remap-alist + '((cpp . c++) + (gomod . go-mod) + (javascript . js) + (vim . vimscript)) + "TODO") + +(setopt treesit-font-lock-level 4) +(setopt treesit-language-source-alist + '((c "https://github.com/tree-sitter/tree-sitter-c") + (cpp "https://github.com/tree-sitter/tree-sitter-cpp") + (css "https://github.com/tree-sitter/tree-sitter-css") + (go "https://github.com/tree-sitter/tree-sitter-go") + (gomod "https://github.com/camdencheek/tree-sitter-go-mod") + (gsp "git://git.thomasvoss.com/tree-sitter-gsp.git") + (html "https://github.com/tree-sitter/tree-sitter-html") + (javascript "https://github.com/tree-sitter/tree-sitter-javascript") + (python "https://github.com/tree-sitter/tree-sitter-python") + (typescript "https://github.com/tree-sitter/tree-sitter-typescript" + "master" "typescript/src") + (vim "https://github.com/tree-sitter-grammars/tree-sitter-vim") + (vue "https://github.com/ikatyang/tree-sitter-vue"))) + + +;;; Install Missing Parsers + +(defun mm-treesit-sync-sources () + "Sync Tree-Sitter parsers. +Reinstall the Tree-Sitter parsers specified by + `treesit-language-source-alist'." + (interactive) + (let ((total (length treesit-language-source-alist)) + (count 0) + (work treesit-language-source-alist) + (processors-to-use (max 1 (1- (num-processors))))) + (while work + (let ((specs (seq-take work processors-to-use))) + (dolist (spec specs) + (async-start + `(lambda () + ,(async-inject-variables "\\`treesit-language-source-alist\\'") + (treesit-install-language-grammar ',(car spec))) + (lambda (_) + (setq count (1+ count)) + (message "Done syncing Tree-Sitter grammar for `%s' [%d/%d]" + (car spec) count total)))) + (setq work (seq-drop work processors-to-use)))))) + +(thread-last + (mapcar #'car treesit-language-source-alist) + (seq-remove #'treesit-language-available-p) + (mapc #'treesit-install-language-grammar)) + + +;;; Install Additional TS Modes + +(use-package gsp-ts-mode + :vc (:url "https://git.thomasvoss.com/gsp-ts-mode" + :branch "master" + :rev :newest + :vc-backend Git) + :ensure t) + +;; NOTE: This package doesn’t autoload its ‘auto-mode-alist’ entries +(use-package vimscript-ts-mode + :ensure t + :mode (rx (or (seq (? (or ?. ?_)) (? ?g) "vimrc") + ".vim" + ".exrc") + eos)) + +;; NOTE: This package doesn’t autoload its ‘auto-mode-alist’ entries +(use-package vue-ts-mode + :vc (:url "https://github.com/8uff3r/vue-ts-mode.git" + :branch "main" + :rev :newest + :vc-backend Git) + :ensure t + :mode "\\.vue\\'") + + +;;; Prefer Tree-Sitter Modes + +;; NOTE: ‘go-ts-mode’ already adds itself to ‘auto-mode-alist’ but it +;; isn’t autoloaded as of 2024-09-29 so we need to do it ourselves +;; anyway. Same goes for ‘typescript-ts-mode’. +(defvar mm-treesit-language-file-name-alist + '((go . "\\.go\\'") + (go-mod . "/go\\.mod\\'") + (typescript . "\\.ts\\'")) + "Alist mapping languages to their associated file-names. +This alist is a set of pairs of the form (LANG . REGEXP) where LANG is +the symbol corresponding to a major mode with the ‘-ts-mode’ suffix +removed. REGEXP is a regular expression matching filenames for which +the associated language’s major-mode should be enabled. + +This alist is used to configure `auto-mode-alist'.") + +(dolist (spec treesit-language-source-alist) + (let* ((lang (car spec)) + (lang (alist-get lang mm-treesit-language-remap-alist lang)) + (symbol-name (symbol-name lang)) + (name-mode (intern (concat symbol-name "-mode"))) + (name-ts-mode (intern (concat symbol-name "-ts-mode")))) + ;; If ‘name-ts-mode’ is already in ‘auto-mode-alist’ then we don’t + ;; need to do anything, however if that’s not the case then if + ;; ‘name-ts-mode’ and ‘name-mode’ are both bound we do a simple + ;; remap. If the above is not true then we lookup the extensions in + ;; ‘mm-treesit-language-file-name-alist’. + (cond + ((not (fboundp name-ts-mode)) + (warn "`%s' is missing." name-ts-mode)) + ((rassq name-ts-mode auto-mode-alist) + nil) + ((fboundp name-mode) + (add-to-list 'major-mode-remap-alist (cons name-mode name-ts-mode))) + (t ; (and (fboundp name-ts-mode) (not (fboundp name-mode))) + (if-let ((file-regexp + (alist-get lang mm-treesit-language-file-name-alist))) + (add-to-list 'auto-mode-alist (cons file-regexp name-ts-mode)) + (warn "Unable to determine the extension for `%s'." name-ts-mode)))))) + + +;;; Hack For C23 + +(advice-add #'c-ts-mode--keywords :filter-return + (defun mm-c-ts-mode-add-constexpr (keywords) + ;; NOTE: We can’t add ‘typeof’ until it’s added to the TS grammar + ;; https://github.com/tree-sitter/tree-sitter-c/issues/236 + (append keywords '("constexpr")))) + +(provide 'mm-treesit) |