;;; +init.el --- extra init.el stuff -*- lexical-binding: t -*- ;;; Commentary: ;; Yes, I edit my init.el often enough I need to write a mode for it. ;;; Code: (require '+lisp) ;;; Sort `setup' forms (defun +init--sexp-setup-p (sexp-str &optional head) "Is SEXP-STR a `setup' form, optionally with a HEAD form?" (let ((head (if (and head (symbolp head)) (symbol-name head) head))) (and (string-match-p (rx (: bos (* whitespace) "(setup")) sexp-str) (if head (string-match-p (concat "\\`.*" head) sexp-str) t)))) (defun +init-sort () "Sort init.el. Sort based on the following heuristic: `setup' forms (the majority of my init.el) are sorted after everything else, and within that group, forms with a HEAD of `:require' are sorted first, and `:straight' HEADs are sorted last. All other forms are sorted lexigraphically." (interactive) ;; I have to make my own "version" of `save-excursion', since the mark and ;; point are lost (I think that's the problem) when sorting the buffer. (let* ((current-point (point)) (current-defun (beginning-of-defun)) (defun-point (- current-point (point))) (current-defun-re (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (widen) ; It makes no sense to `save-restriction' (+lisp-sort-sexps (point-min) (point-max) ;; Key function nil ;; Sort function (lambda (s1 s2) (let ((s1 (cdr s1)) (s2 (cdr s2))) (cond ;; Sort everything /not/ `setup' /before/ `setup' ((and (+init--sexp-setup-p s1) (not (+init--sexp-setup-p s2))) nil) ((and (+init--sexp-setup-p s2) (not (+init--sexp-setup-p s1))) t) ;; otherwise... (t (let ((s1-straight (+init--sexp-setup-p s1 :straight)) (s2-straight (+init--sexp-setup-p s2 :straight)) (s1-require (+init--sexp-setup-p s1 :require)) (s2-require (+init--sexp-setup-p s2 :require))) (cond ;; `:straight' setups have extra processing ((and s1-straight s2-straight) (let* ((r (rx (: ":straight" (? "-when") (* space) (? "(")))) (s1 (replace-regexp-in-string r "" s1)) (s2 (replace-regexp-in-string r "" s2))) (string< s1 s2))) ;; `:require' setups go first ((and s1-require (not s2-require)) t) ((and s2-require (not s1-require)) nil) ;; `:straight' setups go last ((and s1-straight (not s2-straight)) nil) ((and s2-straight (not s1-straight)) t) ;; otherwise, sort lexigraphically (t (string< s1 s2))))))))) ;; Return to original point relative to the defun we were in (ignore-errors (goto-char (point-min)) (re-search-forward current-defun-re) (beginning-of-defun) (goto-char (+ (point) defun-point))))) (defun +init-sort-then-save () "Sort init.el, then save it." (interactive) (+init-sort) (if (fboundp #'user-save-buffer) (user-save-buffer) (save-buffer))) ;;; Add `setup' forms to `imenu-generic-expression' (defun +init-add-setup-to-imenu () "Recognize `setup' forms in `imenu'." ;; `imenu-generic-expression' automatically becomes buffer-local when set (setf (alist-get "Setup" imenu-generic-expression nil nil #'equal) (list (rx (: "(setup" (+ space) (group (? "(") (* nonl)))) 1)) (when (boundp 'consult-imenu-config) (setf (alist-get ?s (plist-get (alist-get 'emacs-lisp-mode consult-imenu-config) :types)) '("Setup")))) ;;; Major mode ;;;###autoload (define-derived-mode +init-mode emacs-lisp-mode "Init.el" "`emacs-lisp-mode', but with a few specialized bits and bobs for init.el.") ;;;###autoload (add-to-list 'auto-mode-alist '("/init\\.el\\'" . +init-mode)) (provide '+init) ;;; +init.el ends here