diff --git a/README.org b/README.org index 9996b72..3dfd6d1 100755 --- a/README.org +++ b/README.org @@ -95,6 +95,35 @@ On boot, ... sydnix-cli is a command-line utility written in Clojure wrapping various sydnix-related scripts. +** Deferring Emacs packages + +Nearly all configuration of Emacs packages happens under the ~use-package~ macro. ~use-package~ has various keywords with special syntax for common tasks, such as instrumenting hooks, setting keybindings, and customising variables. You may be surprised to learn that these are not /just/ syntactic sugar }:) (I was). + +As an example, this + +#+begin_src emacs-lisp +(use-package which-key + :hook (on-first-input . which-key-mode) + :custom + (which-key-allow-evil-operators t) + (which-key-show-operator-state-maps t) +#+end_src + +is not the same as this + +#+begin_src emacs-lisp +(use-package which-key + :config + (add-hook 'on-first-input #'which-key-mode) + (setq which-key-allow-evil-operators t) + (setq which-key-show-operator-state-maps t) +#+end_src + +The difference connects to another silly obsession of the Emacs hacker: startup time. ~use-package~'s special keywords will /defer/ the loading of the package ([cite:@systemcrafters2021how]) . E.g., instead of loading ~which-key~ on startup, it will be loaded when the ~on-first-input~ hook is first called. + +~on-first-input~ is one of many useful hooks provided by the package [[https://github.com/emacsmirror/on][on.el]] specialised for fine-grained control of package loading. + +>>>>>>> Conflict 1 of 1 ends * Tasks ** Begin setting up doomless Emacs diff --git a/users/crumb/programs/emacs/auto-save-list/.saves-5003-nixos-testbed~ b/users/crumb/programs/emacs/auto-save-list/.saves-5003-nixos-testbed~ new file mode 100644 index 0000000..e69de29 diff --git a/users/crumb/programs/emacs/init-straight.el b/users/crumb/programs/emacs/init-straight.el index b13740c..83599e1 100644 --- a/users/crumb/programs/emacs/init-straight.el +++ b/users/crumb/programs/emacs/init-straight.el @@ -13,6 +13,7 @@ "straight/repos/straight.el/bootstrap.el")) (bootstrap-version 7)) (unless (file-exists-p bootstrap-file) + (message "Could not find Straight's bootstrap file. Attempting to download it now.") (let* ((url "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el") (url-buffer (url-retrieve-synchronously url 'silent 'inhibit-cookies))) diff --git a/users/crumb/programs/emacs/init.el b/users/crumb/programs/emacs/init.el index e805dad..68c9abb 100644 --- a/users/crumb/programs/emacs/init.el +++ b/users/crumb/programs/emacs/init.el @@ -11,6 +11,7 @@ (add-to-list 'load-path (file-name-concat user-emacs-directory "modules")) (add-to-list 'load-path (file-name-concat user-emacs-directory "lib")) +(require 'syd-autosave) +(require 'syd-display-startup-time) (require 'syd-evil) (require 'syd-ui) -(require 'syd-autosave) diff --git a/users/crumb/programs/emacs/lib/syd-prelude.el b/users/crumb/programs/emacs/lib/syd-prelude.el index 716dbe7..bcae20f 100644 --- a/users/crumb/programs/emacs/lib/syd-prelude.el +++ b/users/crumb/programs/emacs/lib/syd-prelude.el @@ -11,4 +11,9 @@ (or (getenv "EMACS_CACHE_DIR") (error "Need $EMACS_CACHE_DIR"))) +;; `on.el' provies a collection of utility hooks and functions ported from Doom +;; Emacs. The hooks make it easier to speed up Emacs startup by providing +;; finer-grained control of the timing at which packages are loaded. +(use-package on) + (provide 'syd-prelude) diff --git a/users/crumb/programs/emacs/modules/syd-autosave.el b/users/crumb/programs/emacs/modules/syd-autosave.el index 5da90da..e7c6c05 100644 --- a/users/crumb/programs/emacs/modules/syd-autosave.el +++ b/users/crumb/programs/emacs/modules/syd-autosave.el @@ -19,21 +19,18 @@ (".*" ,(file-name-concat syd-cache-dir "autosave") t)) kept-new-versions 5 - delete-old-versions t - save-place-file (file-name-concat syd-cache-dir "places") - bookmark-default-file (file-name-concat syd-data-dir "bookmarks") - recentf-save-file (file-name-concat syd-data-dir "recentf"))) + delete-old-versions t)) ;; Save your cursor position in recently-opened files. (use-package saveplace - :config - (save-place-mode 1)) + :hook (on-first-file . save-place-mode) + :custom (save-place-file (file-name-concat syd-cache-dir "places"))) ;; Keep track of recently-visited files. (use-package recentf - :config - (add-hook 'find-file-hook #'recentf-save-list) - (recentf-mode 1)) + :hook ((on-first-file . recentf-mode) + (find-file-hook . recentf-save-list)) + :custom (recentf-save-file (file-name-concat syd-data-dir "recentf"))) (provide 'syd-autosave) diff --git a/users/crumb/programs/emacs/modules/syd-display-startup-time.el b/users/crumb/programs/emacs/modules/syd-display-startup-time.el new file mode 100644 index 0000000..d8646dd --- /dev/null +++ b/users/crumb/programs/emacs/modules/syd-display-startup-time.el @@ -0,0 +1,14 @@ +;;; syd-display-startup-time.el -*- lexical-binding: t; -*- + +(defun syd-display-startup-time () + (message "Emacs loaded in %s with %d garbage collections." + (format "%.3f seconds" + (float-time + (time-subtract after-init-time before-init-time))) + gcs-done)) + +(use-package emacs + :hook + (emacs-startup-hook . syd-display-startup-time)) + +(provide 'syd-display-startup-time) diff --git a/users/crumb/programs/emacs/modules/syd-evil.el b/users/crumb/programs/emacs/modules/syd-evil.el index 3deb37c..d316f5b 100644 --- a/users/crumb/programs/emacs/modules/syd-evil.el +++ b/users/crumb/programs/emacs/modules/syd-evil.el @@ -2,10 +2,26 @@ ;; Vim emulation. (use-package evil - :init - (setq evil-want-minibuffer t) - (setq evil-move-beyond-eol t) - (setq evil-vsplit-window-right t) + :preface + (setq evil-want-minibuffer t + evil-move-beyond-eol t + evil-vsplit-window-right t + evil-ex-search-vim-style-regexp t + evil-ex-visual-char-range t + evil-mode-line-format nil + evil-normal-state-cursor 'box + evil-emacs-state-cursor 'hbar + evil-operator-state-cursor 'evil-half-cursor + evil-insert-state-cursor 'bar + evil-visual-state-cursor 'hollow + ;; Only do highlighting in selected window so that Emacs has less work + ;; to do highlighting them all. + evil-ex-interactive-search-highlight 'selected-window + ;; It's infuriating that innocuous "beginning of line" or "end of line" + ;; errors will abort macros, so suppress them: + evil-kbd-macro-suppress-motion-error t + evil-undo-system (cond ((featurep 'undo-tree) 'undo-tree) + ((featurep 'undo-fu) 'undo-fu))) ;; These two are required for evil-collection. (setq evil-want-keybinding nil evil-want-integration t) @@ -20,16 +36,18 @@ ;; integrations. (use-package evil-collection :after evil - :custom - (evil-collection-setup-minibuffer t) + :custom (evil-collection-setup-minibuffer t) :config (evil-collection-init)) ;; Tim Pope's famous `surround.vim' for Evil. (use-package evil-surround - :ensure t + :commands (global-evil-surround-mode + evil-surround-edit + evil-Surround-edit + evil-surround-region) + :hook (on-first-input . global-evil-surround-mode) :config - (global-evil-surround-mode 1) ;; In `emacs-lisp-mode', `' is a much more common pair than ``. (add-hook 'emacs-lisp-mode-hook (lambda () @@ -38,20 +56,21 @@ ;; TODO: I'd like JK to escape visual state. evil-escape only allows defining a ;; single key sequence. Perhaps key-chord is capable of this? (use-package evil-escape - :config - (setq-default evil-escape-key-sequence "jk" - evil-escape-delay 0.15) - (evil-escape-mode 1)) + :hook (on-first-input . evil-escape-mode) + :custom ((evil-escape-key-sequence "jk") + (evil-escape-delay 0.15))) ;; `evil-nerd-commenter' has a bunch of cool functions[1]. Here, only the Evil ;; operator is used. }:3 ;; [1]: https://github.com/redguardtoo/evil-nerd-commenter?tab=readme-ov-file#commands-and-hotkeys (use-package evil-nerd-commenter - :config - (define-key evil-normal-state-map "#" #'evilnc-comment-operator) - (define-key evil-visual-state-map "#" #'evilnc-comment-operator) - (define-key evil-inner-text-objects-map "c" 'evilnc-inner-commenter) - (define-key evil-outer-text-objects-map "c" 'evilnc-outer-commenter)) + :commands (evilnc-comment-operator + evilnc-inner-comment + evilnc-outer-commenter) + :bind (:map evil-normal-state-map ("#" . evilnc-comment-operator) + :map evil-visual-state-map ("#" . evilnc-comment-operator) + :map evil-inner-text-objects-map ("c" . evilnc-inner-comment) + :map evil-outer-text-objects-map ("c" . evilnc-outer-comment))) ;; Enhance `evil-surround' with integration with `embrace'. (use-package evil-embrace @@ -60,68 +79,60 @@ :config (evil-embrace-enable-evil-surround-integration)) -;; Provides a command to swap two regions of text. +;; Provides an Evil operator to swap two spans of text. (use-package evil-exchange - :config - (define-key evil-normal-state-map "gX" 'evil-exchange) - (define-key evil-visual-state-map "gX" 'evil-exchange)) + :bind (:map evil-normal-state-map ("gX" . evil-exchange) + :map evil-visual-state-map ("gX" . evil-exchange))) ;; Evil doesn't ship with support for Vim's 'g-'/'g+'. `evil-numbers' ;; implements this. (use-package evil-numbers - :config ;; 'g=' is a bit more comfortable than 'g+', whilst preserving the analogy. ;; ('=' is '+' modulo shift) - (define-key evil-normal-state-map "g=" 'evil-numbers/inc-at-pt) - (define-key evil-normal-state-map "g-" 'evil-numbers/dec-at-pt)) + :bind (:map evil-normal-state-map ("g=" . 'evil-numbers/inc-at-pt) + :map evil-normal-state-map ("g-" . 'evil-numbers/dec-at-pt))) ;; Tree-sitter queries → Evil text objects. -(use-package evil-textobj-tree-sitter) +(use-package evil-textobj-tree-sitter + :defer t) ;; Visually "flash" the region acted upon by Evil-mode operations. (use-package evil-goggles + :hook (on-first-input . evil-goggles-mode) ;; The flash animation will delay actions, which can be very annoying for some ;; operations. Disable `evil-goggles' for those ones. :custom - (evil-goggles-enable-delete nil) - (evil-goggles-enable-change nil) - (evil-goggles-duration 0.1) - :config - (evil-goggles-mode 1)) + ((evil-goggles-enable-delete nil) + (evil-goggles-enable-change nil) + (evil-goggles-duration 0.1))) ;; Change cursor shape and color by evil state in terminal. (use-package evil-terminal-cursor-changer + ;; This package is only useful in the terminal. :if (not (display-graphic-p)) - :config - (setq evil-motion-state-cursor 'box) - (setq evil-visual-state-cursor 'box) - (setq evil-normal-state-cursor 'box) - (setq evil-insert-state-cursor 'bar) - (setq evil-emacs-state-cursor 'hbar) - (evil-terminal-cursor-changer-activate)) + :hook (on-first-input . evil-terminal-cursor-changer-activate)) ;; Automatic alignment in region, by regexp. (use-package evil-lion - :config - (evil-lion-mode 1)) + :hook (on-first-input . evil-lion-mode)) -;; Provides the 'g' text object which selects the entire buffer. -(use-package evil-textobj-entire) +;; 'g' text object selecting the entire buffer. +(with-eval-after-load 'evil + (evil-define-text-object + evil-entire-buffer (count &optional beg end type) + "Select entire buffer" + (evil-range (point-min) (point-max) type)) + (define-key evil-inner-text-objects-map "g" #'evil-entire-buffer) + (define-key evil-outer-text-objects-map "g" #'evil-entire-buffer)) ;; 2-character search. (use-package evil-snipe - :commands evil-snipe-local-mode evil-snipe-override-local-mode - :hook (on-first-input . evil-snipe-override-mode) - :hook (on-first-input . evil-snipe-mode) - :init - (setq evil-snipe-smart-case t - evil-snipe-scope 'visible - evil-snipe-repeat-scope 'visible - evil-snipe-char-fold t)) - -;; Extend the set of delimiters recognised by '%'. -(use-package evil-matchit - :config - (global-evil-matchit-mode 1)) + :commands (evil-snipe-local-mode evil-snipe-override-local-mode) + :hook ((on-first-input . evil-snipe-override-mode) + (on-first-input . evil-snipe-mode)) + :custom ((evil-snipe-smart-case t) + (evil-snipe-scope 'visible) + (evil-snipe-repeat-scope 'visible) + (evil-snipe-char-fold t))) (provide 'syd-evil) diff --git a/users/crumb/programs/emacs/modules/syd-ui.el b/users/crumb/programs/emacs/modules/syd-ui.el index f2b0f3d..3f70c39 100644 --- a/users/crumb/programs/emacs/modules/syd-ui.el +++ b/users/crumb/programs/emacs/modules/syd-ui.el @@ -2,11 +2,9 @@ ;; Show possible completions for a partially-entered key sequence. (use-package which-key - :custom - (which-key-allow-evil-operators t) - (which-key-show-operator-state-maps t) - :config - (which-key-mode 1)) + :hook (on-first-input . which-key-mode) + :custom ((which-key-allow-evil-operators t) + (which-key-show-operator-state-maps t))) ;; Beautiful theme in dark and light. (use-package kanagawa-themes @@ -16,17 +14,20 @@ (use-package emacs :custom ;; Allow the opening of new minibuffers from inside existing minibuffers. - (enable-recursive-minibuffers t) - ;; Hide commands in M-x which do not work in the current mode. - (read-extended-command-predicate #'command-completion-default-include-p) + ((enable-recursive-minibuffers t) + ;; Hide commands in M-x which do not work in the current mode. + (read-extended-command-predicate #'command-completion-default-include-p)) :config - ;; Disable blinking cursor. I don't really like it, but it also doesn't play - ;; well with `evil-terminal-cursor-changer'. + ;; Disable blinking cursor. Aesthetically, I personally don't fancy it; + ;; technically, it doesn't play well with `evil-terminal-cursor-changer'. (blink-cursor-mode -1)) +;; Vertico is a simple completion engine that replaces Emacs' built-in +;; completion engine, achieving Just Works™ compatibility. This is in contrast +;; to e.g. Helm and Ivy, which spawn ecosystems orthogonal to Emacs, and +;; diametrically-opposed to each other. (use-package vertico - :init - (vertico-mode 1)) + :hook (on-first-input . vertico-mode)) ;; Orderless provides a completion style that divides the pattern into ;; space-separated components, and matches candidates that match all of the @@ -34,8 +35,8 @@ ;; ways: literally, as a regexp, as an initialism, in the flex style, or as ;; multiple word prefixes. By default, regexp and literal matches are enabled. (use-package orderless - :custom - (completion-styles '(orderless basic)) - (completion-category-overrides '((file (styles basic partial-completion))))) + :custom ((completion-styles '(orderless basic)) + (completion-category-overrides + '((file (styles basic partial-completion)))))) (provide 'syd-ui)