init-packages.el 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. ;;; init-packages.el --- Package Configuration File -*- lexical-binding: t -*-
  2. ;;; Commentary:
  3. ;;; Code:
  4. ;; Elpaca installer block
  5. (defvar elpaca-installer-version 0.8)
  6. (defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
  7. (defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
  8. (defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
  9. (defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
  10. :ref nil :depth 1
  11. :files (:defaults "elpaca-test.el" (:exclude "extensions"))
  12. :build (:not elpaca--activate-package)))
  13. (let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory))
  14. (build (expand-file-name "elpaca/" elpaca-builds-directory))
  15. (order (cdr elpaca-order))
  16. (default-directory repo))
  17. (add-to-list 'load-path (if (file-exists-p build) build repo))
  18. (unless (file-exists-p repo)
  19. (make-directory repo t)
  20. (when (< emacs-major-version 28) (require 'subr-x))
  21. (condition-case-unless-debug err
  22. (if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
  23. ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
  24. ,@(when-let* ((depth (plist-get order :depth)))
  25. (list (format "--depth=%d" depth) "--no-single-branch"))
  26. ,(plist-get order :repo) ,repo))))
  27. ((zerop (call-process "git" nil buffer t "checkout"
  28. (or (plist-get order :ref) "--"))))
  29. (emacs (concat invocation-directory invocation-name))
  30. ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
  31. "--eval" "(byte-recompile-directory \".\" 0 'force)")))
  32. ((require 'elpaca))
  33. ((elpaca-generate-autoloads "elpaca" repo)))
  34. (progn (message "%s" (buffer-string)) (kill-buffer buffer))
  35. (error "%s" (with-current-buffer buffer (buffer-string))))
  36. ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  37. (unless (require 'elpaca-autoloads nil t)
  38. (require 'elpaca)
  39. (elpaca-generate-autoloads "elpaca" repo)
  40. (load "./elpaca-autoloads")))
  41. (add-hook 'after-init-hook #'elpaca-process-queues)
  42. (elpaca `(,@elpaca-order))
  43. ;; End of elpaca installer block
  44. ;; Install use-package support
  45. (elpaca elpaca-use-package
  46. ;; Enable use-package :ensure support for Elpaca.
  47. (elpaca-use-package-mode)
  48. (setq use-package-always-ensure t
  49. use-package-always-defer t
  50. package-native-compile t
  51. elpaca-queue-limit 10)
  52. (setq use-package-verbose init-file-debug
  53. use-package-expand-minimally (not init-file-debug)
  54. use-package-compute-statistics nil
  55. debug-on-error init-file-debug)
  56. (bind-key "C-c e u" 'elpaca-fetch-all)
  57. (bind-key "C-c e m" 'elpaca-manager)
  58. (bind-key "C-c e r" 'elpaca-update-menus)
  59. (bind-key "C-c e t" 'elpaca-try)
  60. (bind-key "C-c e b" 'elpaca-rebuild)
  61. (bind-key "C-c e d" 'elpaca-delete))
  62. (elpaca diminish)
  63. ;; Temporary workaround for packages needing newer version of seq (https://github.com/progfolio/elpaca/issues/216#issuecomment-1868444883))
  64. (defun +elpaca-unload-seq (e)
  65. (and (featurep 'seq) (unload-feature 'seq t))
  66. (elpaca--continue-build e))
  67. (defun +elpaca-seq-build-steps ()
  68. (append (butlast (if (file-exists-p (expand-file-name "seq" elpaca-builds-directory))
  69. elpaca--pre-built-steps elpaca-build-steps))
  70. (list '+elpaca-unload-seq 'elpaca--activate-package)))
  71. (elpaca `(seq :build ,(+elpaca-seq-build-steps)))
  72. (defun +elpaca-unload-transient (e)
  73. (and (featurep 'transient) (unload-feature 'transient t))
  74. (elpaca--continue-build e))
  75. (defun +elpaca-transient-build-steps ()
  76. (append (butlast (if (file-exists-p (expand-file-name "transient" elpaca-builds-directory))
  77. elpaca--pre-built-steps elpaca-build-steps))
  78. (list '+elpaca-unload-transient 'elpaca--activate-package)))
  79. (elpaca `(transient :build ,(+elpaca-transient-build-steps)))
  80. ;; Block until current queue processed.
  81. (elpaca-wait)
  82. (add-to-list 'elpaca-ignored-dependencies 'project)
  83. (add-to-list 'elpaca-ignored-dependencies 'xref)
  84. ;; https://github.com/progfolio/elpaca/wiki/Logging#auto-hiding-the-elpaca-log-buffer
  85. (defvar +elpaca-hide-log-commands '(eval-buffer eval-region eval-defun eval-last-sexp org-ctrl-c-ctrl-c eros-eval-defun eros-eval-last-sexp elisp-eval-region-or-buffer)
  86. "List of commands for which a successfully processed log is auto hidden.")
  87. (defun +elpaca-hide-successful-log ()
  88. "Hide Elpaca log buffer if queues processed successfully."
  89. (message "this: %S last: %S" this-command last-command)
  90. (if-let ((incomplete (cl-find 'incomplete elpaca--queues :key #'elpaca-q<-status))
  91. ((elpaca-q<-elpacas incomplete)))
  92. nil
  93. (when-let ((log (bound-and-true-p elpaca-log-buffer))
  94. (window (get-buffer-window log t)) ;; log buffer visible
  95. ((or (member last-command +elpaca-hide-log-commands)
  96. (member this-command +elpaca-hide-log-commands))))
  97. (with-selected-window window (quit-window 'kill window)))))
  98. (add-hook 'elpaca-post-queue-hook #'+elpaca-hide-successful-log)
  99. ;; https://github.com/progfolio/elpaca/wiki/Logging#how-to-change-a-commands-log-query
  100. (with-eval-after-load 'elpaca-log
  101. (setf (alist-get +elpaca-hide-log-commands
  102. elpaca-log-command-queries nil nil #'equal)
  103. "#unique | !finished"))
  104. ;; https://github.com/progfolio/elpaca/wiki/Logging#customizing-the-position-of-the-elpaca-log-buffer
  105. (add-to-list 'display-buffer-alist '("\\*elpaca-log\\*" (display-buffer-reuse-window display-buffer-at-bottom)))
  106. ;; https://github.com/progfolio/elpaca/wiki/Reloading-a-package%E2%80%99s-features-after-updating-a-package
  107. (defun +elpaca-reload-package (package &optional allp)
  108. "Reload PACKAGE's features.
  109. If ALLP is non-nil (interactively, with prefix), load all of its
  110. features; otherwise only load ones that were already loaded.
  111. This is useful to reload a package after upgrading it. Since a
  112. package may provide multiple features, to reload it properly
  113. would require either restarting Emacs or manually unloading and
  114. reloading each loaded feature. This automates that process.
  115. Note that this unloads all of the package's symbols before
  116. reloading. Any data stored in those symbols will be lost, so if
  117. the package would normally save that data, e.g. when a mode is
  118. deactivated or when Emacs exits, the user should do so before
  119. using this command."
  120. (interactive
  121. (list (let ((elpaca-overriding-prompt "Reload package: "))
  122. (elpaca--read-queued))
  123. current-prefix-arg))
  124. ;; This finds features in the currently installed version of PACKAGE, so if
  125. ;; it provided other features in an older version, those are not unloaded.
  126. (when (yes-or-no-p (format "Unload all of %s's symbols and reload its features? " package))
  127. (let* ((package-name (symbol-name package))
  128. (package-dir (file-name-directory
  129. (locate-file package-name load-path (get-load-suffixes))))
  130. (package-files (directory-files package-dir 'full (rx ".el" eos)))
  131. (package-features
  132. (cl-loop for file in package-files
  133. when (with-temp-buffer
  134. (insert-file-contents file)
  135. (when (re-search-forward (rx bol "(provide" (1+ space)) nil t)
  136. (goto-char (match-beginning 0))
  137. (cadadr (read (current-buffer)))))
  138. collect it)))
  139. (unless allp
  140. (setf package-features (seq-intersection package-features features)))
  141. (dolist (feature package-features)
  142. (ignore-errors
  143. ;; Ignore error in case it's not loaded.
  144. (unload-feature feature 'force)))
  145. (dolist (feature package-features)
  146. (require feature))
  147. (when package-features
  148. (message "Reloaded: %s" (mapconcat #'symbol-name package-features " "))))))
  149. (define-advice elpaca-merge (:after (id &optional _fetch _interactive) elpaca-merge-reload)
  150. "Automically reload packages after they have been updated."
  151. (cl-letf (((symbol-function 'yes-or-no-p) (cl-constantly t)))
  152. (when (not (or (memq id elpaca-ignored-dependencies)
  153. ;; TODO why aren't xref and project already in the list?
  154. (memq id '(xref project perspective))))
  155. (+elpaca-reload-package id))))
  156. ;; https://github.com/radian-software/radian/blob/e3aad124c8e0cc870ed09da8b3a4905d01e49769/emacs/radian.el#L352
  157. (defmacro use-feature (name &rest args)
  158. "Like `use-package', but without elpaca integration.
  159. `NAME' and `ARGS' are as with `use-package'"
  160. (declare (indent defun))
  161. `(use-package ,name
  162. :ensure nil
  163. ,@args))
  164. ;; useful for corfu and vertico extensions
  165. (defmacro use-extension (pkg name &rest args)
  166. "Like `use-package', but for a package extension.
  167. `PKG' is the name of the package, `NAME' and `ARGS' are as with `use-package'"
  168. (declare (indent defun))
  169. `(use-package ,name
  170. :ensure nil
  171. :after ,pkg
  172. :demand t
  173. ,@args))
  174. (defun add-to-list* (list-var &rest elts)
  175. "Add `ELTS' to `LIST-VAR'."
  176. (dolist (elt elts)
  177. (add-to-list list-var elt)))
  178. (defun append-to-list* (list-var &rest elts)
  179. "Append `ELTS' to `LIST-VAR'."
  180. (dolist (elt elts)
  181. (add-to-list list-var elt t)))
  182. (provide 'init-packages)
  183. ;;; init-packages.el ends here