summary refs log tree commit diff stats
path: root/lisp/+init.el
blob: c1f3cb56cf6f7e6535c3b692ac798661e864e009 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
;;; +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)
  (save-excursion
    (save-restriction
      (widen)
      (+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))))))))))))

(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 'string-equal)
    (list
     (rx (: bol (* space)
        "(setup" (+ space)
        (group (? "(") (* nonl))))
     1)))

;;; 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