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