;;; init.el --- Emacs configuration file -*- lexical-binding: t; -*- ;; Author: Jessie Hildebrandt ;; Homepage: https://gitlab.com/jessieh/dot-emacs ;; Package-Requires: ((emacs "28.1")) ;; This file is not part of GNU Emacs. ;;; Commentary: ;; jessieh's (Mostly) Portable Emacs Config ;; ;; This configuration file was designed to work with Emacs 28.1, and will not ;; work with earlier versions. You should probably have native-comp enabled. ;; ;; This file will automatically generate an early-init.el file upon first run, ;; or if early-init.el is missing. ;; ;; All configuration efforts here are organized around the use of wrapped ;; `use-package' macros. The keyword order for any config entry should be: ;; - if/when/unless ;; - demand ;; - after ;; - requires ;; - defines/functions ;; - init/config ;; - custom/custom-faces ;; - commands ;; - mode/interpreter/hook ;; - bind/bind*/bind-keymap/bind-keymap* ;; ;; Custom functions and variables should generally be prefixed with "user/". ;; This prefix is purposefully unidiomatic so as to avoid collisions with any ;; package prefixes, inbuilt or otherwise. ;;; License: ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 2, or ;; (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth ;; Floor, Boston, MA 02110-1301, USA. ;;; Code: ;; -------------------------------------------------------------------------- ;; ;; ;; Startup ;; ;; -------------------------------------------------------------------------- ;; (defconst user/font "Fira Code 10" "Default font.") (defconst user/init-file (locate-user-emacs-file "init.el") "Location of user init file.") (defconst user/early-init-file (locate-user-emacs-file "early-init.el") "Location of user early-init file.") ;; ---------------------------------- ;; ;; early-init.el file creation ;; ---------------------------------- ;; (defvar user/early-init-forms `(";;; early-init.el --- Early initialization -*- lexical-binding: t; -*-" ";; This file is not part of GNU Emacs." ";;; Commentary:" ";; This file has been automatically generated by init.el." ";; More relevant commentary is likely in init.el, not here." ";;; Code:" (defconst user/gc-cons-threshold (* 32 1024 1024) "Preferred garbage collection threshold value (32MB).") (defconst user/gc-cons-percentage 0.1 "Preferred garbage collection percentage value (10%).") (defun user/defer-garbage-collection () "Defer garbage collection by maximizing the collection threshold." (setq gc-cons-threshold most-positive-fixnum gc-cons-percentage 1.0)) (defun user/restore-garbage-collection () "Restore the garbage collection threshold parameters in a deferred fashion." (setq gc-cons-threshold user/gc-cons-threshold gc-cons-percentage user/gc-cons-percentage)) ";; Defer garbage collection until after initialization" (user/defer-garbage-collection) (add-hook 'emacs-startup-hook #'user/restore-garbage-collection) ";; Clear `file-name-handler-alist' until after initialization" (let ((default-file-name-handler-alist file-name-handler-alist)) (setq file-name-handler-alist nil) (add-hook 'emacs-startup-hook (lambda () (setq file-name-handler-alist default-file-name-handler-alist)))) ";; Configure GUI components before initial frame creation" (setq inhibit-x-resources t frame-inhibit-implied-resize t default-frame-alist '((width . 100) (height . 40) (font . ,user/font) (menu-bar-lines . 0) (tool-bar-lines . 0) (vertical-scroll-bars . nil) (background-color . "gray15") (foreground-color . "gray85"))) ";; package.el initialization is handled manually in init.el" (setq package-enable-at-startup nil) ";;; init.el ends here") "List of forms that are written to the user early-init file.") (defun user/write-forms-to-file (forms file) "Format and pretty-print list FORMS to FILE." (with-temp-file file (mapcar (lambda (form) (insert (concat (if (stringp form) (prin1-to-string form :no-escape) (pp-to-string form)) "\n"))) forms))) ;; Create (and load) early-init file if it does not yet exist, or if this file ;; is currently being recompiled. We recreate early-init.el during compilation ;; in case `user/early-init-forms' has been changed (unless (file-exists-p user/early-init-file) (user/write-forms-to-file user/early-init-forms user/early-init-file) (load user/early-init-file nil nil :no-suffix)) ;; Make sure that the user init files are byte-compiled. (let ((byte-compile-warnings nil)) (when (file-newer-than-file-p user/early-init-file (concat user/early-init-file "c")) (byte-compile-file user/early-init-file)) (when (file-newer-than-file-p user/init-file (concat user/init-file "c")) (byte-compile-file user/init-file))) ;; ---------------------------------- ;; ;; Package manager initialization ;; ---------------------------------- ;; ;; To be evaluated at both compile time and run time (eval-and-compile (setq package-user-dir (locate-user-emacs-file "package/") package-native-compile t package-check-signature nil use-package-hook-name-suffix nil use-package-always-demand (daemonp))) ;; To be evaluated only at compile time (eval-when-compile ;; Initialize package.el (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (unless (bound-and-true-p package--initialized) (package-initialize)) (package-refresh-contents) ;; Install use-package (unless (package-installed-p 'use-package) (package-install 'use-package)) (require 'use-package) ;; After initialization, build quickstart file containing package autoload defs ;; and compile it alongside any other uncompiled Elisp files in installed packages (add-hook 'emacs-startup-hook (lambda () (let ((byte-compile-warnings nil)) (byte-recompile-directory package-user-dir 0) (package-quickstart-refresh))))) ;; Load "package-quickstart.el" file containing package autoload defs with a ;; call to `package-activate-all'. This is must be done to load any installed ;; packages during initialization outside of compile time because we won't have ;; called `package-initialize'. We also require `bind-key' here, which the ;; compiled `use-package' macros still rely on for handling keybinds (unless (bound-and-true-p package--initialized) (package-activate-all) (require 'bind-key)) ;; -------------------------------------------------------------------------- ;; ;; ;; Custom interactive functions ;; ;; -------------------------------------------------------------------------- ;; (defun user/open-init-file () "Opens the user init file in a new buffer." (interactive) (find-file user/init-file)) (defun user/byte-compile-init-files () "Byte-compile the user init and early-init files." (interactive) (message "Byte-compiling init and early-init file...") (byte-compile-file user/init-file) (byte-compile-file user/early-init-file)) (defun user/download-latest-init-file () "Download the latest user init file from jessieh.net/emacs. The user init file will be automatically recompiled after downloading. If `user/init-file' points to a symlink, nothing will be downloaded." (interactive) (if (file-symlink-p user/init-file) (message "%s is a symlink, operation aborted. Please update the init file manually." user/init-file) (when (yes-or-no-p "Download latest init file from jessieh.net/emacs? ") (message "Updating init file...") (url-copy-file "https://jessieh.net/emacs" user/init-file :ok-if-already-exists) (user/byte-compile-init-files)))) (defun user/refresh-packages () "Refresh packages that have been configured for use in the user init file. This is accomplished by deleting `package-user-dir' and recompiling the user init file, which initializes the package manager during compile time." (interactive) (when (yes-or-no-p "Redownload and refresh packages? ") (message "Refreshing packages...") (delete-directory package-user-dir :recursive) (user/byte-compile-init-files))) (defun user/indent-buffer () "Call `indent-region' on the contents of the active buffer." (interactive) (indent-region (point-min) (point-max))) (defun user/select-minibuffer-window () "Select the minibuffer window if it is active." (interactive) (when (active-minibuffer-window) (select-window (active-minibuffer-window)))) (defun user/scratch-buffer () "Open the scratch buffer, (re)creating it if not present." (interactive) (pop-to-buffer (startup--get-buffer-create-scratch))) (defun user/scan-directory-for-projects () "Prompt for a directory and then scan for any project roots within. Inaccessible directories and .git directories are skipped during searching. When done searching, you will be shown a buffer of all discovered project directories and will be prompted to index them. Choosing to index the project directories will register them with Emacs' project management system." (interactive) (when-let* ((valid-root-p (lambda (path) (and (file-accessible-directory-p path) (not (string= (file-name-nondirectory path) ".git"))))) (directory (read-directory-name "Look for projects in: ")) (project-dir-list (mapcar #'file-name-directory (directory-files-recursively directory "\\.git$" #'valid-root-p t))) (num-projects-found (length project-dir-list)) (temp-buffer-name (concat "*" (number-to-string num-projects-found) " Project Roots Found*"))) (with-output-to-temp-buffer temp-buffer-name (princ (mapconcat #'identity project-dir-list "\n"))) (when (yes-or-no-p "Index all of these directories as projects? ") (unless (fboundp 'project-remember-projects-under) (require 'project)) (mapc #'project-remember-projects-under project-dir-list) (message "%d%s" num-projects-found " directories indexed as projects.")) (with-current-buffer temp-buffer-name (kill-buffer-and-window)))) ;; -------------------------------------------------------------------------- ;; ;; ;; Internal/built-in packages ;; ;; -------------------------------------------------------------------------- ;; (defmacro editor-feature (name docstring &rest args) "Apply NAME and ARGS to `use-package' with `:ensure' defaulted to nil. DOCSTRING is an optional form that is discarded upon expansion." (declare (doc-string 2) (indent defun)) (ignore docstring) `(use-package ,name :ensure nil ,@args)) ;; ---------------------------------- ;; ;; emacs ;; ---------------------------------- ;; (defun user/ensure-region-active (func &rest args) "Apply ARGS to FUNC only if `region-active-p' is non-nil." (when (region-active-p) (apply func args))) (editor-feature emacs "Provides an extensible, customizable, self-documenting real-time display editor." :config ;; Enable upcase/downcase region commands (put 'upcase-region 'disabled nil) (put 'downcase-region 'disabled nil) (advice-add 'upcase-region :around 'user/ensure-region-active) (advice-add 'downcase-region :around 'user/ensure-region-active) :custom ;; Default working directory (default-directory (if (eq system-type 'windows-nt) (setq default-directory (getenv "USERPROFILE")) (setq default-directory "~/"))) ;; General configuration (frame-title-format '("Emacs — %b") "Set frame title to buffer name") (truncate-lines t "Truncate lines instead of wrapping") (message-truncate-lines t "Truncate messages in the echo area") (cursor-in-non-selected-windows nil "Hide cursor in inactive windows") (ring-bell-function 'ignore "Disable terminal bell") (fill-column 80 "Set default line-wrap column to column 80") (max-mini-window-height 10 "Limit minibuffer height to 10 lines") (enable-recursive-minibuffers t "Allow minibuffer commands to be called in the minibuffer") (x-gtk-use-system-tooltips nil "Disable use of system tooltips in favor of Emacs tooltips.") (load-prefer-newer t "Load from source files if they are newer than bytecode files") ;; Startup (initial-scratch-message "" "Leave scratch buffer empty on startup") (initial-major-mode 'fundamental-mode "Set initial mode to fundamental-mode on startup") (inhibit-startup-screen t "Do not create or show the initial splash screen") (inhibit-default-init t "Do not attempt to load any OS-provided init files") ;; Default style rules (indent-tabs-mode nil "Use spaces for indentation") (tab-width 4 "Use 4 spaces for indentation") ;; Scrolling (mouse-wheel-progressive-speed nil "Disable mouse wheel acceleration during scrolling") (scroll-preserve-screen-position 1 "Prevent the cursor from moving during scrolling") (scroll-conservatively 101 "Scroll only one line at a time when cursor leaves view") (scroll-margin 5 "Maintain margin of 5 lines around cursor during scrolling") ;; Performance tweaks (redisplay-skip-fontification-on-input t "Improve redisplay performance while scrolling") (fast-but-imprecise-scrolling t "Improve redisplay performance while scrolling") (jit-lock-defer-time 0 "Defer fontification while input is pending") (auto-window-vscroll nil "Prevent calcuation of arbitrary line heights while scrolling") (auto-mode-case-fold nil "Disable case-insensitive second pass over `auto-mode-alist'") ;; Bidirectional text (bidi-inhibit-bpa t "Disable parentheses matching for bidirectional text") (bidi-display-reordering 'left-to-right "Force global left-to-right text direction") (bidi-paragraph-direction 'left-to-right "Disable paragraph directionality detection") ;; Elisp compilation warnings (native-comp-async-report-warnings-errors nil "Don't report errors from async native compilation") (byte-compile-warnings '(not lexical free-vars noruntime unresolved docstrings)) ;; Set default buffer display action priority: ;; focus a window already displaying the buffer, else display buffer in current window (display-buffer-base-action '((display-buffer-reuse-window display-buffer-same-window))) ;; Set default tooltip frame parameters (tooltip-frame-parameters '((name . "tooltip") (border-width . 1) (internal-border-width . 10))) ;; Filter out buffer-incompatible interactive commands by default (read-extended-command-predicate #'command-completion-default-include-p) :bind ;; General binds ("C-z" . undo) ("C-c SPC" . tmm-menubar) ("C-c DEL" . fixup-whitespace) ("C-c d" . delete-pair) ("C-c i" . overwrite-mode) ("C-c q" . visual-line-mode) ("C-c r" . revert-buffer-quick) ("C-c f" . display-fill-column-indicator-mode) ("C-c \\" . user/indent-buffer) ("C-c o" . user/select-minibuffer-window) ("C-c s" . user/scratch-buffer) ("C-c m" . (lambda () (interactive) (message "%s is in %s" (buffer-name) major-mode))) ;; Navigation ("M-n" . scroll-up-line) ("M-p" . scroll-down-line) ("C-M-n" . forward-paragraph) ("C-M-p" . backward-paragraph) :bind* ;; Window navigation ("C-c C-i" . windmove-up) ("C-c C-k" . windmove-down) ("C-c C-j" . windmove-left) ("C-c C-l" . windmove-right) :hook ;; Minibuffer line spacing setup (minibuffer-setup-hook . (lambda () (setq-local line-spacing 0.15)))) ;; ---------------------------------- ;; ;; autorevert ;; ---------------------------------- ;; (editor-feature autorevert "Displays on-disk changes to files in unmodified buffers automatically." :config (global-auto-revert-mode)) ;; ---------------------------------- ;; ;; display-line-numbers ;; ---------------------------------- ;; (editor-feature display-line-numbers "Displays the absolute number of each line in a buffer." :custom (display-line-numbers-width-start 100 "Count number of lines (+100) in buffer for initial line number width") :hook (prog-mode-hook . (lambda () (unless (derived-mode-p 'lisp-interaction-mode) (display-line-numbers-mode)))) :bind ("C-c n" . display-line-numbers-mode)) ;; ---------------------------------- ;; ;; elec-pair ;; ---------------------------------- ;; (editor-feature elec-pair "Enables automatic pairing of most paired delimiters." :hook (prog-mode-hook . electric-pair-local-mode)) ;; ---------------------------------- ;; ;; eshell ;; ---------------------------------- ;; (defun user/open-eshell (&optional force-new-session) "Focus the last active `eshell' session, or start a new one if none available. When FORCE-NEW-SESSION is non-nil, a new session will be started even if there is already an active session to bring into focus." (interactive) (let ((eshell-buffer (cl-find-if (lambda (buffer) (eq (buffer-local-value 'major-mode buffer) 'eshell-mode)) (buffer-list)))) (if eshell-buffer (if force-new-session (let ((current-buffer-directory default-directory)) (pop-to-buffer eshell-buffer) (let ((default-directory current-buffer-directory)) (eshell :new-session))) (pop-to-buffer eshell-buffer)) (eshell)))) (defun user/auto-toggle-eshell-buffer-tabs () "Enable `tab-line-mode' in all `eshell-mode' buffers if multiple are open. Also automatically disables `tab-line-mode' if all but one `eshell-mode' buffer have been closed." (let ((eshell-buffers (cl-remove-if-not (lambda (buffer) (eq (buffer-local-value 'major-mode buffer) 'eshell-mode)) (buffer-list)))) (mapc (lambda (buffer) (with-current-buffer buffer (tab-line-mode (and (length< eshell-buffers 2) -1)))) eshell-buffers))) (defun user/prettify-eshell-pwd (path) "Return PATH formatted and with all but the last directory name abbreviated." (let* ((components (split-string (abbreviate-file-name path) "/")) (str "")) (while (cdr components) (setq str (concat str (cond ((= 0 (length (car components))) "/") ((= 1 (length (car components))) (concat (car components) "/")) (t (if (string= "." (string (elt (car components) 0))) (concat (substring (car components) 0 2) "/") (string (elt (car components) 0) ?/))))) components (cdr components))) (concat (propertize str 'face 'font-lock-comment-face) (propertize (cl-reduce (lambda (a b) (concat a "/" b)) components) 'face 'font-lock-doc-face)))) (editor-feature eshell "Provides a shell-like interpreter that can process shell or Lisp commands." :custom (eshell-prompt-regexp "^[^#❱\n]* [#❱] ") (eshell-visual-commands '("fish" "bash" "ssh" "mosh" "fzf" "top" "htop" "less")) (eshell-destroy-buffer-when-process-dies t "Destroys child buffers after their process returns") (eshell-error-if-no-glob t "Produces an error if a glob pattern fails to match, like zsh") (eshell-hist-ignoredups t "Treat multiple repeating history entries as a single entry") (eshell-banner-message '(if (executable-find "fortune") (shell-command-to-string "fortune -s computers") "Hello, commander.")) (eshell-prompt-function (lambda () (concat "\n" (user/prettify-eshell-pwd (eshell/pwd)) (if (= (user-uid) 0) " # " " ❱ ")))) :hook (eshell-post-command-hook . (lambda () (rename-buffer (concat eshell-buffer-name " " (user/prettify-eshell-pwd (eshell/pwd))) :unique))) (eshell-mode-hook . (lambda () (setq-local line-spacing 0.15 global-hl-line-mode nil tab-line-tabs-function #'tab-line-tabs-mode-buffers))) (buffer-list-update-hook . user/auto-toggle-eshell-buffer-tabs) :bind* ("C-c RET" . user/open-eshell) ("C-c C-" . (lambda () (interactive) (user/open-eshell :force-new-session)))) ;; `eshell-mode-map' is not available until the `esh-mode' module is loaded, so ;; the local keymap bindings are set up here (editor-feature esh-mode "Handles input from the user and provides `eshell-mode' for eshell." :bind (:map eshell-mode-map ("C-" . eshell-copy-old-input) ("C-c j" . tab-line-switch-to-prev-tab) ("C-c l" . tab-line-switch-to-next-tab))) ;; `eshell-command-aliases-list' is not available until the `em-alias' module is loaded, ;; so the alias additions are set up here (editor-feature em-alias "Handles creation and management of command aliases for eshell." :config (eval-after-load 'em-alias (lambda () (add-to-list 'eshell-command-aliases-list '("lsl" "ls -lh $1"))))) ;; ---------------------------------- ;; ;; files ;; ---------------------------------- ;; (defconst user/custom-file (locate-user-emacs-file "custom.el") "Location of user customizations file.") (defconst user/backup-directory (locate-user-emacs-file "backup/") "Location of user backup directory.") (defconst user/auto-save-directory (locate-user-emacs-file "auto-save/") "Location of user auto save directory.") (defconst user/lock-file-directory (locate-user-emacs-file "lock-file/") "Location of user lock file directory.") (make-directory user/backup-directory :parents) (make-directory user/auto-save-directory :parents) (make-directory user/lock-file-directory :parents) (editor-feature files "Defines most of Emacs' file-handling functionality." :custom (version-control t "Use version numbers on backup files") (kept-new-versions 5 "Keep 5 recent backup files") (kept-old-versions 3 "Keep 3 old backup files") (delete-old-versions t "Clean up old backup files") (backup-by-copying t "Use copying unconditionally when creating backups") (custom-file user/custom-file "Store customization info in a separate file") (backup-directory-alist `((".*" . ,user/backup-directory)) "Set backup directory location") (auto-save-file-name-transforms `((".*" ,user/auto-save-directory t)) "Set auto save directory location") (lock-file-name-transforms `((".*" ,user/lock-file-directory t)) "Set lock file directory location") :hook (before-save-hook . delete-trailing-whitespace)) ;; ---------------------------------- ;; ;; frame ;; ---------------------------------- ;; (defvar user/initial-frame-created nil "Whether or not the first frame has been initialized by the server.") (defvar user/after-daemon-make-initial-frame-functions nil "Run when the first frame is produced by the server in daemon mode.") (defun user/set-up-frame (frame) "Set up newly-created frame FRAME." (when (display-graphic-p frame) (let ((winid (frame-parameter frame 'outer-window-id))) (start-process "" nil "xprop" "-f" "_GTK_THEME_VARIANT" "8u" "-set" "_GTK_THEME_VARIANT" "dark" "-id" winid)) (when (and (daemonp) (not user/initial-frame-created)) (with-selected-frame frame (run-hooks 'user/after-daemon-make-initial-frame-functions) (setq user/initial-frame-created t))))) (editor-feature frame "Graphical frame configuration." :config (mapc #'user/set-up-frame (frame-list)) :hook (after-make-frame-functions . user/set-up-frame)) ;; ---------------------------------- ;; ;; hl-line ;; ---------------------------------- ;; (editor-feature hl-line "Highlights the current line in a buffer." :config (global-hl-line-mode)) ;; ---------------------------------- ;; ;; ispell ;; ---------------------------------- ;; (editor-feature ispell "Checks for spelling errors and suggests corrections from a dictionary." :bind ("C-c ' '" . ispell) ("C-c ' w" . ispell-word) ("C-c ' r" . ispell-region) ("C-c ' b" . ispell-buffer) ("C-c ' ;" . ispell-comment-or-string-at-point) ("C-c ' C-;" . ispell-comments-and-strings)) ;; ---------------------------------- ;; ;; paren ;; ---------------------------------- ;; (editor-feature paren "Highlights matching delimiter pairs under the cursor." :config (show-paren-mode) :custom (show-paren-delay 0.0 "Highlight matching delimiters instantly")) ;; ---------------------------------- ;; ;; savehist ;; ---------------------------------- ;; (editor-feature savehist "Persists minibuffer history between sessions." :config (savehist-mode)) ;; ---------------------------------- ;; ;; server ;; ---------------------------------- ;; (editor-feature server "Allows Emacs to operate as a server for other Emacs processes." :config ;; When running in a server/client configuration, we don't want the ;; foreground/background colors in `default-frame-alist' to overwrite the ;; colors set by the active theme every time we open a new client frame (when (daemonp) (assoc-delete-all 'foreground-color default-frame-alist) (assoc-delete-all 'background-color default-frame-alist)) :custom (server-client-instructions nil "Suppress help messages from the server for new frames")) ;; ---------------------------------- ;; ;; so-long ;; ---------------------------------- ;; (editor-feature so-long "Deactivates certain editor features when opening files with very long lines." :config (global-so-long-mode)) ;; ---------------------------------- ;; ;; subword ;; ---------------------------------- ;; (editor-feature subword "Enables detection of subwords as words in camel case or pascal case names." :config (global-subword-mode)) ;; ---------------------------------- ;; ;; tab-bar ;; ---------------------------------- ;; (editor-feature tab-bar "Provides a frame-wide tab bar that allows for tabbed workspace switching." :custom (tab-bar-format '(tab-bar-format-tabs tab-bar-separator)) (tab-bar-close-button-show nil "Disable close button on tabs")) ;; ---------------------------------- ;; ;; tab-line ;; ---------------------------------- ;; (editor-feature tab-line "Provides a buffer-local tab line that facilitates quick buffer switching." :custom (tab-line-close-button-show nil "Disable close button on tabs") (tab-line-new-button-show nil "Disable tab creation button") (tab-line-left-button nil "Disable the left scroll button") (tab-line-right-button nil "Disable the right scroll button") (tab-line-switch-cycling t "Enable wrap-around tab cycling")) ;; ---------------------------------- ;; ;; uniquify ;; ---------------------------------- ;; (editor-feature uniquify "Provides better unique names when there are name conflicts between buffers." :custom (uniquify-buffer-name-style 'forward "Show file path before buffer name") (uniquify-after-kill-buffer-p t "Update buffer names after killing") (uniquify-ignore-buffers-re "^\\*" "Avoid renaming special buffers")) ;; ---------------------------------- ;; ;; whitespace ;; ---------------------------------- ;; (editor-feature whitespace "Visualizes blank characters." :custom (whitespace-line-column nil "Use the value of `fill-column' instead of a set value") (whitespace-display-mappings '((tab-mark 9 [8677 9]) ; Tab (space-mark 32 [8729]) ; Space (space-mark 160 [8999]) ; Non-breaking space (newline-mark 10 [8626 10])))) ; Newline ;; -------------------------------------------------------------------------- ;; ;; ;; External packages ;; ;; -------------------------------------------------------------------------- ;; (defmacro external-package (name docstring &rest args) "Apply NAME and ARGS to `use-package' with `:ensure' defaulted to t. DOCSTRING is an optional form that is discarded upon expansion." (declare (doc-string 2) (indent defun)) (ignore docstring) `(use-package ,name :ensure t ,@args)) ;; ---------------------------------- ;; ;; Theme setup ;; ---------------------------------- ;; (external-package adwaita-dark-theme "Provides the `adwaita-dark' theme and custom fringe bitmaps." :config (load-theme 'adwaita-dark :no-confirm) (adwaita-dark-theme-arrow-fringe-bmp-enable) (eval-after-load 'diff-hl #'adwaita-dark-theme-diff-hl-fringe-bmp-enable) (eval-after-load 'flymake #'adwaita-dark-theme-flymake-fringe-bmp-enable) (eval-after-load 'flycheck #'adwaita-dark-theme-flycheck-fringe-bmp-enable) (eval-after-load 'neotree #'adwaita-dark-theme-neotree-configuration-enable) :custom (adwaita-dark-theme-bold-vertico-current t "Embolden the currently-selected candidate in vertico") (adwaita-dark-theme-gray-rainbow-delimiters t "Use a gray color for rainbow-delimiters faces") :hook (user/after-daemon-make-initial-frame-functions . (lambda () (load-theme 'adwaita-dark :no-confirm)))) ;; ---------------------------------- ;; ;; Simple language modes ;; ---------------------------------- ;; (external-package clojure-mode "Major mode for Clojure." :mode ("\\.clj\\'" . clojure-mode) ("\\.cljs\\'" . clojurescript-mode) ("\\.cljc\\'" . clojurec-mode)) (external-package csharp-mode "Major mode for C#." :mode ("\\.cs\\'" . csharp-mode)) (external-package dart-mode "Major mode for Dart." :mode ("\\.dart\\'" . dart-mode)) (external-package docker-compose-mode "Major mdoe for docker-compose files." :mode ("\\docker-compose.yml\\'" . docker-compose-mode)) (external-package dockerfile-mode "Major mode for Docker files." :mode ("\\Dockerfile\\'" . dockerfile-mode)) (external-package fennel-mode "Major mode for Fennel." :mode ("\\.fnl\\'" . fennel-mode)) (external-package fish-mode "Major mode for fish files." :mode ("\\.fish\\'" . fish-mode)) (external-package gdscript-mode "Major mode for GDScript." :mode ("\\.gd\\'" . gdscript-mode) ("\\.tscn\\'" . gdscript-mode)) (external-package git-modes "Major modes for git files." :mode ("\\.gitignore\\'" . gitignore-mode) ("\\.gitconfig\\'" . gitconfig-mode) ("\\.gitmodules\\'" . gitconfig-mode) ("\\.gitattributes\\'" . gitattributes-mode)) (external-package glsl-mode "Major mode for GLSL." :mode ("\\.frag\\'" . glsl-mode) ("\\.vert\\'" . glsl-mode)) (external-package json-mode "Major mode for JSON files." :mode ("\\.json\\'" . json-mode)) (external-package kotlin-mode "Major mode for Kotlin." :mode ("\\.kt\\'" . kotlin-mode)) (external-package lua-mode "Major mode for Lua." :mode (("\\.lua\\'" . lua-mode) ("\\.rockspec\\'" . lua-mode))) (external-package rust-mode "Major mode for Rust." :mode ("\\.rs\\'" . rust-mode)) (external-package typescript-mode "Major mode for TypeScript." :mode ("\\.tsx?\\'" . typescript-mode)) (external-package web-mode "Major mode for web templates." :custom (web-mode-markup-indent-offset 2 "Use 2 spaces instead of 4 for indenting HTML elements") (web-mode-enable-auto-quoting nil "Do not automatically insert quotes after HTML attributes") :mode (("\\.php\\'" . web-mode) ("\\.html\\'" . web-mode))) ;; ---------------------------------- ;; ;; anzu ;; ---------------------------------- ;; (external-package anzu "Displays matching and replacement information in the mode line." :config (global-anzu-mode) :bind ("M-s r ." . anzu-query-replace-at-cursor) (" " . anzu-query-replace) (" " . anzu-query-replace-regexp)) ;; ---------------------------------- ;; ;; bufler ;; ---------------------------------- ;; (external-package bufler "Presents open buffers in a grouped and organized menu." :config ;; Set `line-spacing' to a custom value in bufler buffers for some ;; added visual separation between UI elements (add-hook 'bufler-list-mode-hook (lambda () (setq-local line-spacing 0.15))) :custom (bufler-columns '("Name" "Path")) (bufler-list-group-separators '((0 . "\n"))) (bufler-column-name-modified-buffer-sigil " ●") (bufler-list-display-buffer '((display-buffer-reuse-window display-buffer-same-window))) (bufler-groups (bufler-defgroups (group ;; Subgroup collecting all named workspaces. (auto-workspace)) (group ;; Subgroup collecting all `help-mode' and `info-mode' buffers. (group-or "Help/Info" (mode-match "Help" (rx bos "help-")) (mode-match "Info" (rx bos "info-")))) (group ;; Subgroup collecting all special buffers (i.e. ones that are not file-backed), ;; except certain ones like Dired, Forge, or Magit buffers (which are allowed to ;; fall through to other groups, so they end up grouped with their project buffers). (group-not "Special" (group-or "Special" (mode-match "Eshell" (rx bos "eshell-")) (mode-match "Magit" (rx bos "magit-")) (mode-match "Forge" (rx bos "forge-")) (mode-match "Dired" (rx bos "dired")) (mode-match "Grep" (rx bos "grep-")) (mode-match "Compilation" (rx bos "compilation-")) (auto-file))) (group ;; Subgroup collecting these "special special" buffers ;; separately for convenience. (name-match "Emacs" (rx bos "*" (or "Messages" "Warnings" "scratch" "Backtrace") "*"))) (group ;; Subgroup collecting all other Magit buffers, grouped by directory. (mode-match "Magit" (rx bos "magit-")) (auto-directory)) ;; Remaining special buffers are grouped automatically by mode. (auto-mode)) (group ;; Subgroup collecting buffers in a version-control project, ;; grouping them by directory (using the parent project keeps, ;; e.g. git worktrees with their parent repos). (auto-parent-project) (group-or "Files" (auto-file)) ;; Subgroup collecting special buffers so they are easily distinguished from file buffers. (group-not "Special" (auto-file))) ;; Group remaining buffers by directory, then major mode. (auto-directory) (auto-mode))) :bind ("C-x C-b" . bufler-list)) ;; ---------------------------------- ;; ;; cider ;; ---------------------------------- ;; (defun user/abbreviate-ns (namespace) "Return NAMESPACE with all but the last name abbreviated." (let* ((abbreviated-ns (cider-abbreviate-ns "some-class.wow.git-mirror-magic.core")) (names (reverse (split-string abbreviated-ns "\\."))) (last-name (car names))) (concat (mapconcat (lambda (name) (propertize (concat name ".") 'face 'font-lock-comment-face)) (reverse (cdr names)) "") (propertize last-name 'face 'font-lock-doc-face)))) (external-package cider "Provides an interactive programming environment for Clojure." :custom (nrepl-hide-special-buffers t "Hide REPL communications buffers from buffer lists.") (cider-session-name-template "%j" "Label CIDER sessions with the short name of the current project.") (cider-reply-display-in-current-window t "Use current window when creating a CIDER REPL buffer.") (cider-use-fringe-indicators nil "Disable evaluation indicators in the fringe of CIDER buffers.") (cider-repl-display-help-banner nil "Disable initial infodump in CIDER REPL buffers.") (cider-repl-prompt-function (lambda (namespace) (concat (user/abbreviate-ns namespace) " ❱ "))) :config (setq nrepl-repl-buffer-name-template "*CIDER: %s (%r:%S)*" cider--debug-buffer-format "*CIDER: Debugging %s*" cider-inspector-buffer "*CIDER: Inspect*" cider-error-buffer "*CIDER: Error*") :commands (cider) :hook (clojure-mode-hook . cider-mode) :bind (:map clojure-mode-map ("C-c RET" . nil)) (:map cider-mode-map ("C-c RET" . nil) ("C-c C-f" . cider-load-file) ("C-c C-b" . cider-load-buffer)) (:map cider-repl-mode-map ("C-c RET" . nil) ("C-c C-e" . end-of-buffer) ("C-" . (lambda () (interactive) (when (and (get-text-property (point) 'cider-old-input) (< (point) cider-repl-input-start-mark)) (cider-repl--grab-old-input nil)))))) ;; ---------------------------------- ;; ;; consult ;; ---------------------------------- ;; (external-package consult "Provides a number of autocompletion-enhanced replacements for default commands." :config ;; `eshell-mode-map' is not available until the `esh-mode' module is loaded (eval-after-load 'esh-mode (lambda () (bind-key "C-r" #'consult-history 'eshell-mode-map))) :bind ("M-C-s" . (lambda () (interactive) (consult-line (thing-at-point 'symbol)))) ("M-s l" . consult-line) ("M-s L" . consult-line-multi) ("C-S-s" . consult-line-multi) ("C-x M-b" . consult-buffer-other-window) (" " . consult-goto-line) (" " . consult-line) (" " . consult-buffer) (" " . consult-yank-from-kill-ring) (" " . consult-ripgrep)) ;; ---------------------------------- ;; ;; corfu ;; ---------------------------------- ;; (external-package corfu "Enhances completion suggestions with a visible popup." :init (global-corfu-mode) :config (corfu-popupinfo-mode) (corfu-history-mode) (add-to-list 'savehist-additional-variables 'corfu-history) ;; Set `line-spacing' to a custom value in corfu buffers for some ;; added visual separation between completion candidates (advice-add 'corfu--make-buffer :around (lambda (orig-fun &rest args) (let ((line-spacing 0.20)) (apply orig-fun args)))) :custom (corfu-auto t "Automatically display popups wherever available") (corfu-min-width 20 "Ensure completion popups are at least 20 columns wide") (corfu-max-width 50 "Limit completion popup width to 50 columns") (corfu-popupinfo-hide nil "Don't hide doc popups while scrolling between candidates") (corfu-popupinfo-delay 0.1 "Wait 0.1 seconds before showing a doc popup for a candidate") (corfu-popupinfo-max-width 70 "Limit doc popup width to 70 columns") (corfu-popupinfo-min-height 4 "Ensure doc popups are at least 4 lines tall") (corfu-echo-documentation nil "Disable displaying documentation strings in the echo area") :hook ;; Displaying popups aggressively (i.e. without summoning them with a key press) can ;; cause the cursor to jump around in `eshell-mode' (eshell-mode-hook . (lambda () (setq-local corfu-auto nil))) :bind (:map corfu-popupinfo-map ("M-n" . corfu-popupinfo-scroll-up) ("M-p" . corfu-popupinfo-scroll-down))) ;; ---------------------------------- ;; ;; corfu-terminal ;; ---------------------------------- ;; (external-package corfu-terminal "Allows Corfu to function when not running in a graphical frame." :when (not (or (daemonp) (display-graphic-p))) :config (corfu-terminal-mode)) ;; ---------------------------------- ;; ;; diff-hl ;; ---------------------------------- ;; (external-package diff-hl "Highlights uncommited changes to version controlled files in the gutter." :config (global-diff-hl-mode) (diff-hl-flydiff-mode)) ;; ---------------------------------- ;; ;; eshell-up ;; ---------------------------------- ;; (external-package eshell-up "Provides a shortcut for quickly navigating to parent directories in eshell." :config ;; `eshell-command-aliases-list' is not available until the `em-alias' module is loaded (eval-after-load 'em-alias (lambda () (add-to-list 'eshell-command-aliases-list '("up" "eshell-up $1")) (add-to-list 'eshell-command-aliases-list '("pk" "eshell-up-peek $1"))))) ;; ---------------------------------- ;; ;; fish-completion ;; ---------------------------------- ;; (external-package fish-completion "Sources shell completion candidates from the fish shell." :when (executable-find "fish") :hook (eshell-mode-hook . fish-completion-mode)) ;; ---------------------------------- ;; ;; flycheck ;; ---------------------------------- ;; (external-package flycheck "Enables on-the-fly syntax checking for supported languages." :custom (flycheck-idle-change-delay 2 "Wait for 2 seconds of idling before invoking any checkers") (flycheck-check-syntax-automatically '(save idle-change mode-enabled)) :hook (prog-mode-hook . flycheck-mode) :bind ("C-c e" . flycheck-list-errors)) ;; ---------------------------------- ;; ;; fussy ;; ---------------------------------- ;; (external-package fussy "Provides a flexible completion style that scores and sorts candidates." :custom (completion-ignore-case t "Ignore case in completion candidates") (completion-category-defaults nil "Disable category-specific completion styles") (completion-category-overrides nil "Disable category-specific completion styles") (completion-styles '(substring fussy basic) "Set fussy as a fallback completion style")) ;; ---------------------------------- ;; ;; hl-todo ;; ---------------------------------- ;; (external-package hl-todo "Highlights certain keywords in comments." :custom (hl-todo-keyword-faces '(("TODO" . hl-todo) ("TEMP" . hl-todo) ("HACK" . hl-todo) ("DEBUG" . hl-todo) ("FIXME" . hl-todo))) :hook (prog-mode-hook . hl-todo-mode)) ;; ---------------------------------- ;; ;; kind-icon ;; ---------------------------------- ;; (external-package kind-icon "Adds contextual icons in front of Corfu completion candidates." :config (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter) :custom (kind-icon-use-icons nil "Use stylized text labels instead of graphical badges") (kind-icon-blend-frac 0.25 "Brighten background colors with heavy blending from foreground colors") (kind-icon-default-face 'corfu-default "Stylize characters using the same face as Corfu")) ;; ---------------------------------- ;; ;; ligature ;; ---------------------------------- ;; (external-package ligature "Enables support for mapping characters to ligatures." :config (ligature-set-ligatures 'prog-mode '(;; This set of ligatures is for Fira Code, but ;; should work for most any font with ligatures ;; && &&& ;; ;; ;;; ;; %% %%% ;; ?? ??? ?: ?= ?. ;; !! !!! !. !: !!. != !== !~ (";" (rx (+ ";"))) ("&" (rx (+ "&"))) ("%" (rx (+ "%"))) ("?" (rx (or ":" "=" "\." (+ "?")))) ("!" (rx (+ (or "=" "!" "\." ":" "~")))) ;; \\ \\\ \/ ;; ++ +++ ++++ +> ;; :: ::: :::: :> :< := :// ::= ;; // /// //// /\ /* /> /===:===!=//===>>==>==/ ;; == === ==== => =| =>>=>=|=>==>> ==< =/=//=// =~ =:= =!= ("\\" (rx (or "/" (+ "\\")))) ("+" (rx (or ">" (+ "+")))) (":" (rx (or ">" "<" "=" "//" ":=" (+ ":")))) ("/" (rx (+ (or ">" "<" "|" "/" "\\" "\*" ":" "!" "=")))) ("=" (rx (+ (or ">" "<" "|" "/" "~" ":" "!" "=")))) ;; |> ||> |||> ||||> |] |} || ||| |-> ||-|| ;; |->>-||-<<-| |- |== ||=|| |==>>==<<==<=>==//==/=!==:===> ("|" (rx (+ (or ">" "<" "|" "/" ":" "!" "}" "\]" "-" "=" )))) ;; *> */ *) ** *** **** ;; .. ... .... .= .- .? ..= ..< ;; -- --- ---- -~ -> ->> -| -|->-->>->--<<-| ;; #: #= #! #( #? #[ #{ #_ #_( ## ### ##### ;; >: >- >>- >--|-> >>-|-> >= >== >>== >=|=:=>> >> >>> >>>> ("*" (rx (or ">" "/" ")" (+ "*")))) ("\." (rx (or "=" "-" "\?" "\.=" "\.<" (+ "\.")))) ("-" (rx (+ (or ">" "<" "|" "~" "-")))) ("#" (rx (or ":" "=" "!" "(" "\?" "\[" "{" "_(" "_" (+ "#")))) (">" (rx (+ (or ">" "<" "|" "/" ":" "=" "-")))) ;; <>