Updating Doom Emacs.

This commit is contained in:
Derek Taylor
2020-06-19 22:43:40 -05:00
parent 0f664d532a
commit a5c86c514a
453 changed files with 13527 additions and 12455 deletions

View File

@@ -36,7 +36,7 @@ it if it doesn't exist).")
;;
;; Functions
;;; Functions
;;;###autoload
(defun doom-buffer-frame-predicate (buf)
@@ -139,6 +139,8 @@ If BUFFER-OR-NAME is omitted or nil, the current buffer is tested."
(stringp buffer-or-name)
(signal 'wrong-type-argument (list '(bufferp stringp) buffer-or-name)))
(when-let (buf (get-buffer buffer-or-name))
(when-let (basebuf (buffer-base-buffer buf))
(setq buf basebuf))
(and (buffer-live-p buf)
(not (doom-temp-buffer-p buf))
(or (buffer-local-value 'doom-real-buffer-p buf)
@@ -195,7 +197,9 @@ If DERIVED-P, test with `derived-mode-p', otherwise use `eq'."
;;;###autoload
(defun doom-set-buffer-real (buffer flag)
"Forcibly mark BUFFER as FLAG (non-nil = real)."
"Forcibly mark BUFFER as FLAG (non-nil = real).
See `doom-real-buffer-p' for an explanation for real buffers."
(with-current-buffer buffer
(setq doom-real-buffer-p flag)))
@@ -251,7 +255,9 @@ regex PATTERN. Returns the number of killed buffers."
;;;###autoload
(defun doom-mark-buffer-as-real-h ()
"Hook function that marks the current buffer as real."
"Hook function that marks the current buffer as real.
See `doom-real-buffer-p' for an explanation for real buffers."
(doom-set-buffer-real (current-buffer) t))
@@ -272,6 +278,12 @@ If DONT-SAVE, don't prompt to save modified buffers (discarding their changes)."
(set-buffer-modified-p nil)))
(doom-kill-buffer-fixup-windows buffer))
(defun doom--message-or-count (interactive message count)
(if interactive
(message message count)
count))
;;;###autoload
(defun doom/kill-all-buffers (&optional buffer-list interactive)
"Kill all buffers and closes their windows.
@@ -286,14 +298,14 @@ belong to the current project."
(if (null buffer-list)
(message "No buffers to kill")
(save-some-buffers)
(delete-other-windows)
(when (memq (current-buffer) buffer-list)
(switch-to-buffer (doom-fallback-buffer)))
(mapc #'doom-kill-buffer-and-windows buffer-list)
(delete-other-windows)
(when interactive
(message "Killed %s buffers"
(- (length buffer-list)
(length (cl-remove-if-not #'buffer-live-p buffer-list)))))))
(mapc #'kill-buffer buffer-list)
(doom--message-or-count
interactive "Killed %d buffers"
(- (length buffer-list)
(length (cl-remove-if-not #'buffer-live-p buffer-list))))))
;;;###autoload
(defun doom/kill-other-buffers (&optional buffer-list interactive)
@@ -308,10 +320,10 @@ project."
(doom-buffer-list)))
t))
(mapc #'doom-kill-buffer-and-windows buffer-list)
(when interactive
(message "Killed %s buffers"
(- (length buffer-list)
(length (cl-remove-if-not #'buffer-live-p buffer-list))))))
(doom--message-or-count
interactive "Killed %d other buffers"
(- (length buffer-list)
(length (cl-remove-if-not #'buffer-live-p buffer-list)))))
;;;###autoload
(defun doom/kill-matching-buffers (pattern &optional buffer-list interactive)
@@ -341,10 +353,10 @@ current project."
(if current-prefix-arg (doom-project-buffer-list)))
t))
(mapc #'kill-buffer buffer-list)
(when interactive
(message "Killed %s buried buffers"
(- (length buffer-list)
(length (cl-remove-if-not #'buffer-live-p buffer-list))))))
(doom--message-or-count
interactive "Killed %d buried buffers"
(- (length buffer-list)
(length (cl-remove-if-not #'buffer-live-p buffer-list)))))
;;;###autoload
(defun doom/kill-project-buffers (project &optional interactive)
@@ -362,9 +374,9 @@ current project."
nil)
t))
(when project
(let ((buffers (doom-project-buffer-list project)))
(doom-kill-buffers-fixup-windows buffers)
(when interactive
(message "Killed %d buffer(s)"
(- (length buffers)
(length (cl-remove-if-not #'buffer-live-p buffers))))))))
(let ((buffer-list (doom-project-buffer-list project)))
(doom-kill-buffers-fixup-windows buffer-list)
(doom--message-or-count
interactive "Killed %d project buffers"
(- (length buffer-list)
(length (cl-remove-if-not #'buffer-live-p buffer-list)))))))

View File

@@ -1,95 +0,0 @@
;;; core/autoload/cache.el -*- lexical-binding: t; -*-
;; This little library thinly wraps around persistent-soft (which is a pcache
;; wrapper, how about that). It has three purposes:
;;
;; + To encapsulate the cache backend (persistent-soft/pcache in this case), in
;; case it needs to change.
;; + To provide `doom-cache-persist': a mechanism for easily persisting
;; variables across Emacs sessions.
;; + To lazy-load persistent-soft until it is really needed.
;;
;; Like persistent-soft, caches assume a 2-tier structure, where all caches are
;; namespaced by location.
(defvar doom-cache-alists '(t)
"An alist of alists, containing lists of variables for the doom cache library
to persist across Emacs sessions.")
(defvar doom-cache-location 'doom
"The default location for cache files. This symbol is translated into a file
name under `pcache-directory' (by default a subdirectory under
`doom-cache-dir'). One file may contain multiple cache entries.")
(defun doom-save-persistent-cache-h ()
"Hook to run when an Emacs session is killed. Saves all persisted variables
listed in `doom-cache-alists' to files."
(dolist (alist (butlast doom-cache-alists 1))
(cl-loop with key = (car alist)
for var in (cdr alist)
if (symbol-value var)
do (doom-cache-set var it nil key))))
(add-hook 'kill-emacs-hook #'doom-save-persistent-cache-h)
;;
;; Library
;;;###autoload
(defmacro with-cache! (location &rest body)
"Runs BODY with a different default `doom-cache-location'."
(declare (indent defun))
`(let ((doom-cache-location ',location))
,@body))
;;;###autoload
(defun doom-cache-persist (location variables)
"Persist VARIABLES (list of symbols) in LOCATION (symbol).
This populates these variables with cached values, if one exists, and saves them
to file when Emacs quits.
Warning: this is incompatible with buffer-local variables."
(dolist (var variables)
(when (doom-cache-exists var location)
(set var (doom-cache-get var location))))
(setf (alist-get location doom-cache-alists)
(append variables (cdr (assq location doom-cache-alists)))))
;;;###autoload
(defun doom-cache-desist (location &optional variables)
"Unregisters VARIABLES (list of symbols) in LOCATION (symbol) from
`doom-cache-alists', thus preventing them from being saved between sessions.
Does not affect the actual variables themselves or their values."
(if variables
(setf (alist-get location doom-cache-alists)
(cl-set-difference (cdr (assq location doom-cache-alists))
variables))
(delq (assq location doom-cache-alists)
doom-cache-alists)))
;;;###autoload
(defun doom-cache-get (key &optional location)
"Retrieve KEY from LOCATION (defaults to `doom-cache-location'), if it exists
and hasn't expired."
(persistent-soft-fetch
key (symbol-name (or location doom-cache-location))))
;;;###autoload
(defun doom-cache-set (key value &optional ttl location)
"Set KEY to VALUE in the cache. TTL is the time (in seconds) until this cache
entry expires. LOCATION is the super-key to store this cache item under; the
default is `doom-cache-location'. "
(persistent-soft-store
key value
(symbol-name (or location doom-cache-location)) ttl))
;;;###autoload
(defun doom-cache-exists (key &optional location)
"Returns t if KEY exists at LOCATION (defaults to `doom-cache-location')."
(persistent-soft-exists-p key (or location doom-cache-location)))
;;;###autoload
(defun doom-cache-clear (&optional location)
"Clear a cache LOCATION (defaults to `doom-cache-location')."
(persistent-soft-flush (or location doom-cache-location)))

View File

@@ -1,45 +0,0 @@
;;; core/autoload/cli.el -*- lexical-binding: t; -*-
;;
;;; Library
;;;###autoload
(defun doom-call-process (command &rest args)
"Execute COMMAND with ARGS synchronously.
Returns (STATUS . OUTPUT) when it is done, where STATUS is the returned error
code of the process and OUTPUT is its stdout output."
(with-temp-buffer
(cons (or (apply #'call-process command nil t nil args)
-1)
(string-trim (buffer-string)))))
;;;###autoload
(defun doom-exec-process (command &rest args)
"Execute COMMAND with ARGS synchronously.
Unlike `doom-call-process', this pipes output to `standard-output' on the fly to
simulate 'exec' in the shell, so batch scripts could run external programs
synchronously without sacrificing their output.
Warning: freezes indefinitely on any stdin prompt."
;; FIXME Is there any way to handle prompts?
(with-temp-buffer
(cons (let ((process
(make-process :name "doom-sh"
:buffer (current-buffer)
:command (cons command args)
:connection-type 'pipe))
done-p)
(set-process-filter
process (lambda (_process output)
(princ output (current-buffer))
(princ output)))
(set-process-sentinel
process (lambda (process _event)
(when (memq (process-status process) '(exit stop))
(setq done-p t))))
(while (not done-p)
(sit-for 0.1))
(process-exit-status process))
(string-trim (buffer-string)))))

View File

@@ -26,8 +26,9 @@
(doom-project-find-file doom-private-dir))
;;;###autoload
(defun doom/goto-doomblock ()
"Open your private init.el and go to your `doom!' block."
(defun doom/goto-private-init-file ()
"Open your private init.el file.
And jumps to your `doom!' block."
(interactive)
(find-file (expand-file-name "init.el" doom-private-dir))
(goto-char
@@ -37,13 +38,13 @@
(point))))
;;;###autoload
(defun doom/goto-config-file ()
(defun doom/goto-private-config-file ()
"Open your private config.el file."
(interactive)
(find-file (expand-file-name "config.el" doom-private-dir)))
;;;###autoload
(defun doom/goto-packages-file ()
(defun doom/goto-private-packages-file ()
"Open your private packages.el file."
(interactive)
(find-file (expand-file-name "packages.el" doom-private-dir)))
@@ -52,42 +53,48 @@
;;
;;; Managements
(cl-defmacro doom--compile (command &key on-success on-failure)
(declare (indent defun))
`(with-current-buffer (compile ,command)
(add-hook
'compilation-finish-functions
(lambda (_buf status)
(if (equal status "finished\n")
,on-success
,on-failure))
nil 'local)))
(defmacro doom--if-compile (command on-success &optional on-failure)
(declare (indent 2))
(let ((windowsym (make-symbol "doom-sync-window")))
`(with-current-buffer (compile ,command t)
(let ((,windowsym (get-buffer-window (current-buffer))))
(select-window ,windowsym)
(add-hook
'compilation-finish-functions
(lambda (_buf status)
(if (equal status "finished\n")
(progn
(delete-window ,windowsym)
,on-success)
,on-failure))
nil 'local)))))
;;;###autoload
(defun doom/reload ()
"Reloads your private config.
This is experimental! It will try to do as `bin/doom refresh' does, but from
within this Emacs session. i.e. it reload autoloads files (if necessary),
reloads your package list, and lastly, reloads your private config.el.
This is experimental! It will try to do as `bin/doom sync' does, but from within
this Emacs session. i.e. it reload autoloads files (if necessary), reloads your
package list, and lastly, reloads your private config.el.
Runs `doom-reload-hook' afterwards."
(interactive)
(require 'core-cli)
(when (and IS-WINDOWS (file-exists-p doom-env-file))
(warn "Can't regenerate envvar file from within Emacs. Run 'doom env' from the console"))
(doom--compile (format "%s refresh -e" doom-bin)
:on-success
(let ((doom-reloading-p t))
(doom-initialize 'force)
(with-demoted-errors "PRIVATE CONFIG ERROR: %s"
(general-auto-unbind-keys)
(unwind-protect
(doom-initialize-modules 'force)
(general-auto-unbind-keys t)))
(run-hook-wrapped 'doom-reload-hook #'doom-try-run-hook)
(print! (success "Config successfully reloaded!")))
:on-failure
(message "Can't regenerate envvar file from within Emacs. Run 'doom env' from the console"))
;; In case doom/reload is run before incrementally loaded packages are loaded,
;; which could cause odd load order issues.
(mapc #'require (cdr doom-incremental-packages))
(doom--if-compile (format "%s sync -e" doom-bin)
(let ((doom-reloading-p t))
(doom-initialize 'force)
(with-demoted-errors "PRIVATE CONFIG ERROR: %s"
(general-auto-unbind-keys)
(unwind-protect
(doom-initialize-modules 'force)
(general-auto-unbind-keys t)))
(run-hook-wrapped 'doom-reload-hook #'doom-try-run-hook)
(message "Config successfully reloaded!"))
(user-error "Failed to reload your config")))
;;;###autoload
@@ -98,13 +105,13 @@ This is much faster and safer than `doom/reload', but not as comprehensive. This
reloads your package and module visibility, but does not install new packages or
remove orphaned ones. It also doesn't reload your private config.
It is useful to only pull in changes performed by 'doom refresh' on the command
It is useful to only pull in changes performed by 'doom sync' on the command
line."
(interactive)
(require 'core-cli)
(require 'core-packages)
(doom-initialize-packages)
(doom-cli-reload-autoloads nil 'force))
(doom-autoloads-reload))
;;;###autoload
(defun doom/reload-env (&optional arg)
@@ -118,25 +125,22 @@ imported into Emacs."
(interactive "P")
(when IS-WINDOWS
(user-error "Cannot reload envvar file from within Emacs on Windows, run it from cmd.exe"))
(doom--compile
(format "%s -ic '%s env%s'"
(string-trim
(shell-command-to-string
(format "getent passwd %S | cut -d: -f7"
(user-login-name))))
doom-bin (if arg " -c" ""))
:on-success
(let ((doom-reloading-p t))
(unless arg
(doom-load-envvars-file doom-env-file)))
:on-failure
(doom--if-compile
(format "%s -ic '%s env%s'"
(string-trim
(shell-command-to-string
(format "getent passwd %S | cut -d: -f7"
(user-login-name))))
doom-bin (if arg " -c" ""))
(let ((doom-reloading-p t))
(unless arg
(doom-load-envvars-file doom-env-file)))
(error "Failed to generate env file")))
;;;###autoload
(defun doom/upgrade ()
"Run 'doom upgrade' then prompt to restart Emacs."
(interactive)
(doom--compile (format "%s upgrade" doom-bin)
:on-success
(when (y-or-n-p "You must restart Emacs for the upgrade to take effect.\n\nRestart Emacs?")
(doom/restart-and-restore))))
(doom--if-compile (format "%s -y upgrade" doom-bin)
(when (y-or-n-p "You must restart Emacs for the upgrade to take effect.\n\nRestart Emacs?")
(doom/restart-and-restore))))

View File

@@ -1,5 +1,37 @@
;;; core/autoload/debug.el -*- lexical-binding: t; -*-
;;
;;; Doom's debug mode
;;;###autoload
(defvar doom-debug-variables
'(doom-debug-p
init-file-debug
debug-on-error
garbage-collection-messages
use-package-verbose
jka-compr-verbose
lsp-log-io
gcmh-verbose
magit-refresh-verbose
url-debug)
"A list of variable to toggle on `doom-debug-mode'.")
;;;###autoload
(define-minor-mode doom-debug-mode
"Toggle `debug-on-error' and `doom-debug-p' for verbose logging."
:init-value doom-debug-p
:global t
(let ((value
(cond ((eq arg 'toggle) (not doom-debug-mode))
((> (prefix-numeric-value arg) 0)))))
(mapc (doom-rpartial #'set value) doom-debug-variables)
(message "Debug mode %s" (if value "on" "off"))))
;;
;;; Hooks
;;;###autoload
(defun doom-run-all-startup-hooks-h ()
"Run all startup Emacs hooks. Meant to be executed after starting Emacs with
@@ -17,36 +49,44 @@
;;
;;; Helpers
(defun doom-template-insert (template)
"TODO"
(let ((file (expand-file-name (format "templates/%s" template) doom-core-dir)))
(when (file-exists-p file)
(insert-file-contents file))))
(defsubst doom--collect-forms-in (file form)
(when (file-readable-p file)
(let (forms)
(with-temp-buffer
(insert-file-contents file)
(let (emacs-lisp-mode) (emacs-lisp-mode))
(while (re-search-forward (format "(%s " (regexp-quote form)) nil t)
(let ((ppss (syntax-ppss)))
(unless (or (nth 4 ppss)
(nth 3 ppss))
(save-excursion
(goto-char (match-beginning 0))
(push (sexp-at-point) forms)))))
(nreverse forms)))))
;;;###autoload
(defun doom-info ()
"Returns diagnostic information about the current Emacs session in markdown,
ready to be pasted in a bug report on github."
(require 'vc-git)
(require 'core-packages)
(let ((default-directory doom-emacs-dir)
(doom-modules (doom-modules)))
(cl-letf
(((symbol-function 'sh)
(lambda (&rest args)
(cdr (apply #'doom-call-process args)))))
(doom-modules (doom-module-list)))
(letf! (defun sh (&rest args) (cdr (apply #'doom-call-process args)))
`((emacs
(version . ,emacs-version)
(features ,@system-configuration-features)
(build . ,(format-time-string "%b %d, %Y" emacs-build-time))
(buildopts ,system-configuration-options)
(windowsys . ,(if noninteractive 'batch window-system))
(windowsys . ,(if doom-interactive-p window-system 'batch))
(daemonp . ,(cond ((daemonp) 'daemon)
((and (require 'server)
(server-running-p))
'server-running))))
(doom
(version . ,doom-version)
(build . ,(sh "git" "log" "-1" "--format=%D %h %ci")))
(build . ,(sh "git" "log" "-1" "--format=%D %h %ci"))
(dir . ,(abbreviate-file-name (file-truename doom-private-dir))))
(system
(type . ,system-type)
(config . ,system-configuration)
@@ -79,14 +119,19 @@ ready to be pasted in a bug report on github."
'("n/a")))
(packages
,@(or (condition-case e
(cl-loop for (name . plist) in (doom-package-list)
if (cl-find :private (plist-get plist :modules)
:key #'car)
collect
(if-let (splist (doom-plist-delete (copy-sequence plist)
:modules))
(prin1-to-string (cons name splist))
name))
(mapcar
#'cdr (doom--collect-forms-in
(doom-path doom-private-dir "packages.el")
"package!"))
(error (format "<%S>" e)))
'("n/a")))
(unpin
,@(or (condition-case e
(mapcan #'identity
(mapcar
#'cdr (doom--collect-forms-in
(doom-path doom-private-dir "packages.el")
"unpin!")))
(error (format "<%S>" e)))
'("n/a")))
(elpa
@@ -109,13 +154,13 @@ branch and commit."
(interactive)
(require 'vc-git)
(let ((default-directory doom-core-dir))
(print! "Doom v%s (Emacs v%s)\nBranch: %s\nCommit: %s\nBuild date: %s"
(print! "Doom v%s (%s)\nEmacs v%s\nBranch: %s\nBuild date: %s"
doom-version
(or (vc-git-working-revision doom-core-dir)
"n/a")
emacs-version
(or (vc-git--symbolic-ref doom-core-dir)
"n/a")
(or (vc-git-working-revision doom-core-dir)
"n/a")
(or (cdr (doom-call-process "git" "log" "-1" "--format=%ci"))
"n/a"))))
@@ -127,22 +172,23 @@ markdown and copies it to your clipboard, ready to be pasted into bug reports!"
(let ((buffer (get-buffer-create "*doom-info*"))
(info (doom-info)))
(with-current-buffer buffer
(unless (or noninteractive
(eq major-mode 'markdown-mode)
(not (fboundp 'markdown-mode)))
(markdown-mode))
(or (not doom-interactive-p)
(eq major-mode 'markdown-mode)
(not (fboundp 'markdown-mode))
(markdown-mode))
(erase-buffer)
(if raw
(progn
(save-excursion
(pp info (current-buffer)))
(when (search-forward "(modules " nil t)
(goto-char (match-beginning 0))
(cl-destructuring-bind (beg . end)
(bounds-of-thing-at-point 'sexp)
(let ((sexp (prin1-to-string (sexp-at-point))))
(delete-region beg end)
(insert sexp)))))
(dolist (sym '(modules packages))
(when (re-search-forward (format "^ *\\((%s\\)" sym) nil t)
(goto-char (match-beginning 1))
(cl-destructuring-bind (beg . end)
(bounds-of-thing-at-point 'sexp)
(let ((sexp (prin1-to-string (sexp-at-point))))
(delete-region beg end)
(insert sexp))))))
(insert "<details>\n\n```\n")
(dolist (group info)
(insert! "%-8s%-10s %s\n"
@@ -153,7 +199,7 @@ markdown and copies it to your clipboard, ready to be pasted into bug reports!"
(insert! (indent 8 "%-10s %s\n")
((car spec) (cdr spec)))))
(insert "```\n</details>"))
(if noninteractive
(if (not doom-interactive-p)
(print! (buffer-string))
(switch-to-buffer buffer)
(kill-new (buffer-string))
@@ -161,15 +207,45 @@ markdown and copies it to your clipboard, ready to be pasted into bug reports!"
;;;###autoload
(defun doom/am-i-secure ()
"Test to see if your root certificates are securely configured in emacs."
"Test to see if your root certificates are securely configured in emacs.
Some items are not supported by the `nsm.el' module."
(declare (interactive-only t))
(interactive)
(unless (string-match-p "\\_<GNUTLS\\_>" system-configuration-features)
(warn "gnutls support isn't built into Emacs, there may be problems"))
(if-let* ((bad-hosts
(cl-loop for bad
in '("https://wrong.host.badssl.com/"
"https://self-signed.badssl.com/")
in '("https://expired.badssl.com/"
"https://wrong.host.badssl.com/"
"https://self-signed.badssl.com/"
"https://untrusted-root.badssl.com/"
;; "https://revoked.badssl.com/"
;; "https://pinning-test.badssl.com/"
"https://sha1-intermediate.badssl.com/"
"https://rc4-md5.badssl.com/"
"https://rc4.badssl.com/"
"https://3des.badssl.com/"
"https://null.badssl.com/"
"https://sha1-intermediate.badssl.com/"
;; "https://client-cert-missing.badssl.com/"
"https://dh480.badssl.com/"
"https://dh512.badssl.com/"
"https://dh-small-subgroup.badssl.com/"
"https://dh-composite.badssl.com/"
"https://invalid-expected-sct.badssl.com/"
;; "https://no-sct.badssl.com/"
;; "https://mixed-script.badssl.com/"
;; "https://very.badssl.com/"
"https://subdomain.preloaded-hsts.badssl.com/"
"https://superfish.badssl.com/"
"https://edellroot.badssl.com/"
"https://dsdtestprovider.badssl.com/"
"https://preact-cli.badssl.com/"
"https://webpack-dev-server.badssl.com/"
"https://captive-portal.badssl.com/"
"https://mitm-software.badssl.com/"
"https://sha1-2016.badssl.com/"
"https://sha1-2017.badssl.com/")
if (condition-case _e
(url-retrieve-synchronously bad)
(error nil))
@@ -193,48 +269,59 @@ markdown and copies it to your clipboard, ready to be pasted into bug reports!"
(file (make-temp-file "doom-sandbox-")))
(require 'package)
(with-temp-file file
(insert
(prin1-to-string
(macroexp-progn
(append `((setq noninteractive nil
doom-debug-mode t
load-path ',load-path
package--init-file-ensured t
package-user-dir ,package-user-dir
package-archives ',package-archives
user-emacs-directory ,doom-emacs-dir)
(with-eval-after-load 'undo-tree
;; undo-tree throws errors because `buffer-undo-tree' isn't
;; corrrectly initialized
(setq-default buffer-undo-tree (make-undo-tree))))
(pcase mode
(`vanilla-doom+ ; Doom core + modules - private config
`((load-file ,(expand-file-name "core.el" doom-core-dir))
(doom-initialize)
(doom-initialize-core)
(add-hook 'window-setup-hook #'doom-display-benchmark-h)
(setq doom-modules ',doom-modules)
(maphash (lambda (key plist)
(let ((doom--current-module key)
(doom--current-flags (plist-get plist :flags)))
(load! "init" (doom-module-locate-path (car key) (cdr key)) t)))
doom-modules)
(maphash (lambda (key plist)
(let ((doom--current-module key)
(doom--current-flags (plist-get plist :flags)))
(load! "config" (doom-module-locate-path (car key) (cdr key)) t)))
doom-modules)
(run-hook-wrapped 'doom-init-modules-hook #'doom-try-run-hook)
(doom-run-all-startup-hooks-h)))
(`vanilla-doom ; only Doom core
`((load-file ,(expand-file-name "core.el" doom-core-dir))
(doom-initialize)
(doom-initialize-core)
(doom-run-all-startup-hooks-h)))
(`vanilla ; nothing loaded
`((package-initialize)))))))
"\n(unwind-protect (progn\n" contents "\n)\n"
(format "(delete-file %S))" file)))
(prin1 `(progn
(setq noninteractive nil
process-environment ',doom--initial-process-environment
exec-path ',doom--initial-exec-path
init-file-debug t
load-path ',load-path
package--init-file-ensured t
package-user-dir ,package-user-dir
package-archives ',package-archives
user-emacs-directory ,doom-emacs-dir)
(with-eval-after-load 'undo-tree
;; undo-tree throws errors because `buffer-undo-tree' isn't
;; correctly initialized
(setq-default buffer-undo-tree (make-undo-tree)))
(ignore-errors
(delete-directory ,(expand-file-name "auto-save-list" doom-emacs-dir) 'parents)))
(current-buffer))
(prin1 `(unwind-protect
(defun --run-- () ,(read (concat "(progn\n" contents "\n)")))
(delete-file ,file))
(current-buffer))
(prin1 (pcase mode
(`vanilla-doom+ ; Doom core + modules - private config
`(progn
(load-file ,(expand-file-name "core.el" doom-core-dir))
(setq doom-modules-dirs (list doom-modules-dir))
(let ((doom-init-modules-p t))
(doom-initialize)
(doom-initialize-core-modules))
(setq doom-modules ',doom-modules)
(maphash (lambda (key plist)
(doom-module-put
(car key) (cdr key)
:path (doom-module-locate-path (car key) (cdr key))))
doom-modules)
(--run--)
(maphash (doom-module-loader doom-module-init-file) doom-modules)
(maphash (doom-module-loader doom-module-config-file) doom-modules)
(run-hook-wrapped 'doom-init-modules-hook #'doom-try-run-hook)
(doom-run-all-startup-hooks-h)))
(`vanilla-doom ; only Doom core
`(progn
(load-file ,(expand-file-name "core.el" doom-core-dir))
(let ((doom-init-modules-p t))
(doom-initialize)
(doom-initialize-core-modules))
(--run--)
(doom-run-all-startup-hooks-h)))
(`vanilla ; nothing loaded
`(progn
(package-initialize)
(--run--))))
(current-buffer)))
(let ((args (if (eq mode 'doom)
(list "-l" file)
(list "-Q" "-l" file))))
@@ -254,10 +341,10 @@ markdown and copies it to your clipboard, ready to be pasted into bug reports!"
(delete-file file)
(signal (car e) (cdr e)))))))
(fset 'doom--run-vanilla-emacs (lambda! (doom--run-sandbox 'vanilla)))
(fset 'doom--run-vanilla-doom (lambda! (doom--run-sandbox 'vanilla-doom)))
(fset 'doom--run-vanilla-doom+ (lambda! (doom--run-sandbox 'vanilla-doom+)))
(fset 'doom--run-full-doom (lambda! (doom--run-sandbox 'doom)))
(fset 'doom--run-vanilla-emacs (cmd! (doom--run-sandbox 'vanilla)))
(fset 'doom--run-vanilla-doom (cmd! (doom--run-sandbox 'vanilla-doom)))
(fset 'doom--run-vanilla-doom+ (cmd! (doom--run-sandbox 'vanilla-doom+)))
(fset 'doom--run-full-doom (cmd! (doom--run-sandbox 'doom)))
(defvar doom-sandbox-emacs-lisp-mode-map
(let ((map (make-sparse-keymap)))
@@ -293,7 +380,7 @@ to reproduce bugs and determine if Doom is to blame."
(doom-sandbox-emacs-lisp-mode)
(setq-local default-directory doom-emacs-dir)
(unless (buffer-live-p exists)
(doom-template-insert "VANILLA_SANDBOX")
(insert-file-contents (doom-glob doom-core-dir "templates/VANILLA_SANDBOX"))
(let ((contents (substitute-command-keys (buffer-string))))
(erase-buffer)
(insert contents "\n")))
@@ -328,17 +415,3 @@ will be automatically appended to the result."
(profiler-report)
(profiler-stop))
(setq doom--profiler (not doom--profiler)))
;;;###autoload
(defun doom/toggle-debug-mode (&optional arg)
"Toggle `debug-on-error' and `doom-debug-mode' for verbose logging."
(interactive (list (or current-prefix-arg 'toggle)))
(let ((value
(cond ((eq arg 'toggle) (not doom-debug-mode))
((> (prefix-numeric-value arg) 0)))))
(setq doom-debug-mode value
debug-on-error value
jka-compr-verbose value
lsp-log-io value
gcmh-verbose value)
(message "Debug mode %s" (if value "on" "off"))))

View File

@@ -22,23 +22,20 @@ Returns (approximately):
This is used by `file-exists-p!' and `project-file-exists-p!'."
(declare (pure t) (side-effect-free t))
(let ((exists-fn (if (fboundp 'projectile-file-exists-p)
#'projectile-file-exists-p
#'file-exists-p)))
(if (and (listp spec)
(memq (car spec) '(or and)))
(cons (car spec)
(mapcar (doom-rpartial #'doom--resolve-path-forms directory)
(cdr spec)))
(let ((filevar (make-symbol "file")))
`(let* ((file-name-handler-alist nil)
(,filevar ,spec))
(and (stringp ,filevar)
,(if directory
`(let ((default-directory ,directory))
(,exists-fn ,filevar))
(list exists-fn filevar))
,filevar))))))
(if (and (listp spec)
(memq (car spec) '(or and)))
(cons (car spec)
(mapcar (doom-rpartial #'doom--resolve-path-forms directory)
(cdr spec)))
(let ((filevar (make-symbol "file")))
`(let* ((file-name-handler-alist nil)
(,filevar ,spec))
(and (stringp ,filevar)
,(if directory
`(let ((default-directory ,directory))
(file-exists-p ,filevar))
`(file-exists-p ,filevar))
,filevar)))))
(defun doom--path (&rest segments)
(let (file-name-handler-alist)
@@ -108,26 +105,26 @@ be relative to it.
The search recurses up to DEPTH and no further. DEPTH is an integer.
MATCH is a string regexp. Only entries that match it will be included."
(let (file-name-handler-alist
result)
(let (result file-name-handler-alist)
(dolist (file (mapcan (doom-rpartial #'doom-glob "*") (doom-enlist paths)))
(cond ((file-directory-p file)
(nconcq! result
(and (memq type '(t dirs))
(string-match-p match file)
(not (and filter (funcall filter file)))
(not (and (file-symlink-p file)
(not follow-symlinks)))
(<= mindepth 0)
(list (cond (map (funcall map file))
(relative-to (file-relative-name file relative-to))
(file))))
(and (>= depth 1)
(apply #'doom-files-in file
(append (list :mindepth (1- mindepth)
:depth (1- depth)
:relative-to relative-to)
rest)))))
(appendq!
result
(and (memq type '(t dirs))
(string-match-p match file)
(not (and filter (funcall filter file)))
(not (and (file-symlink-p file)
(not follow-symlinks)))
(<= mindepth 0)
(list (cond (map (funcall map file))
(relative-to (file-relative-name file relative-to))
(file))))
(and (>= depth 1)
(apply #'doom-files-in file
(append (list :mindepth (1- mindepth)
:depth (1- depth)
:relative-to relative-to)
rest)))))
((and (memq type '(t files))
(string-match-p match file)
(not (and filter (funcall filter file)))
@@ -203,53 +200,29 @@ single file or nested compound statement of `and' and `or' statements."
;;
;;; Helpers
(defun doom--forget-file (old-path &optional new-path)
"Ensure `recentf', `projectile' and `save-place' forget OLD-PATH."
(when (bound-and-true-p recentf-mode)
(when new-path
(recentf-add-file new-path))
(recentf-remove-if-non-kept old-path))
(when (and (bound-and-true-p projectile-mode)
(doom-project-p)
(projectile-file-cached-p old-path (doom-project-root)))
(projectile-purge-file-from-cache old-path))
(when (bound-and-true-p save-place-mode)
(save-place-forget-unreadable-files)))
(defun doom--update-file (path)
(when (featurep 'vc)
(vc-file-clearprops path)
(vc-resynch-buffer path nil t))
(when (featurep 'magit)
(magit-refresh)))
(defun doom--copy-file (old-path new-path &optional force-p)
(let* ((new-path (expand-file-name new-path))
(old-path (file-truename old-path))
(new-path (apply #'expand-file-name
(if (or (directory-name-p new-path)
(file-directory-p new-path))
(list (file-name-nondirectory old-path) new-path)
(list new-path))))
(new-path-dir (file-name-directory new-path))
(project-root (doom-project-root))
(short-new-name (if (and project-root (file-in-directory-p new-path project-root))
(file-relative-name new-path project-root)
(abbreviate-file-name new-path))))
(unless (file-directory-p new-path-dir)
(make-directory new-path-dir t))
(when (buffer-modified-p)
(save-buffer))
(cond ((file-equal-p old-path new-path)
(throw 'status 'overwrite-self))
((and (file-exists-p new-path)
(not force-p)
(not (y-or-n-p (format "File already exists at %s, overwrite?" short-new-name))))
(throw 'status 'aborted))
((file-exists-p old-path)
(copy-file old-path new-path t)
short-new-name)
(short-new-name))))
(defun doom--update-files (&rest files)
"Ensure FILES are updated in `recentf', `magit' and `save-place'."
(let (toplevels)
(dolist (file files)
(when (featurep 'vc)
(vc-file-clearprops file)
(when-let (buffer (get-file-buffer file))
(with-current-buffer buffer
(vc-refresh-state))))
(when (featurep 'magit)
(when-let (default-directory (magit-toplevel (file-name-directory file)))
(cl-pushnew default-directory toplevels)))
(unless (file-readable-p file)
(when (bound-and-true-p recentf-mode)
(recentf-remove-if-non-kept file))
(when (and (bound-and-true-p projectile-mode)
(doom-project-p)
(projectile-file-cached-p file (doom-project-root)))
(projectile-purge-file-from-cache file))))
(dolist (default-directory toplevels)
(magit-refresh))
(when (bound-and-true-p save-place-mode)
(save-place-forget-unreadable-files))))
;;
@@ -257,68 +230,69 @@ single file or nested compound statement of `and' and `or' statements."
;;;###autoload
(defun doom/delete-this-file (&optional path force-p)
"Delete FILENAME (defaults to the file associated with current buffer) and
kills the buffer. If FORCE-P, force the deletion (don't ask for confirmation)."
"Delete PATH, kill its buffers and expunge it from vc/magit cache.
If PATH is not specified, default to the current buffer's file.
If FORCE-P, delete without confirmation."
(interactive
(list (file-truename (buffer-file-name))
(list (buffer-file-name (buffer-base-buffer))
current-prefix-arg))
(let* ((fbase (file-name-sans-extension (file-name-nondirectory path)))
(buf (current-buffer)))
(cond ((not (file-exists-p path))
(error "File doesn't exist: %s" path))
((not (or force-p (y-or-n-p (format "Really delete %s?" fbase))))
(message "Aborted")
nil)
((unwind-protect
(progn (delete-file path) t)
(let ((short-path (file-relative-name path (doom-project-root))))
(if (file-exists-p path)
(error "Failed to delete %s" short-path)
;; Ensures that windows displaying this buffer will be switched
;; to real buffers (`doom-real-buffer-p')
(doom/kill-this-buffer-in-all-windows buf t)
(doom--forget-file path)
(doom--update-file path)
(message "Successfully deleted %s" short-path))))))))
(let* ((path (or path (buffer-file-name (buffer-base-buffer))))
(short-path (abbreviate-file-name path)))
(unless (and path (file-exists-p path))
(user-error "Buffer is not visiting any file"))
(unless (file-exists-p path)
(error "File doesn't exist: %s" path))
(unless (or force-p (y-or-n-p (format "Really delete %S?" short-path)))
(user-error "Aborted"))
(let ((buf (current-buffer)))
(unwind-protect
(progn (delete-file path) t)
(if (file-exists-p path)
(error "Failed to delete %S" short-path)
;; Ensures that windows displaying this buffer will be switched to
;; real buffers (`doom-real-buffer-p')
(doom/kill-this-buffer-in-all-windows buf t)
(doom--update-files path)
(message "Deleted %S" short-path))))))
;;;###autoload
(defun doom/copy-this-file (new-path &optional force-p)
"Copy current buffer's file to NEW-PATH. If FORCE-P, overwrite the destination
file if it exists, without confirmation."
"Copy current buffer's file to NEW-PATH.
If FORCE-P, overwrite the destination file if it exists, without confirmation."
(interactive
(list (read-file-name "Copy file to: ")
current-prefix-arg))
(pcase (catch 'status
(when-let (dest (doom--copy-file (buffer-file-name) new-path force-p))
(doom--update-file new-path)
(message "File successfully copied to %s" dest)))
(`overwrite-self (error "Cannot overwrite self"))
(`aborted (message "Aborted"))
(_ t)))
(unless (and buffer-file-name (file-exists-p buffer-file-name))
(user-error "Buffer is not visiting any file"))
(let ((old-path (buffer-file-name (buffer-base-buffer)))
(new-path (expand-file-name new-path)))
(make-directory (file-name-directory new-path) 't)
(copy-file old-path new-path (or force-p 1))
(doom--update-files old-path new-path)
(message "File copied to %S" (abbreviate-file-name new-path))))
;;;###autoload
(defun doom/move-this-file (new-path &optional force-p)
"Move current buffer's file to NEW-PATH. If FORCE-P, overwrite the destination
file if it exists, without confirmation."
"Move current buffer's file to NEW-PATH.
If FORCE-P, overwrite the destination file if it exists, without confirmation."
(interactive
(list (read-file-name "Move file to: ")
current-prefix-arg))
(pcase (catch 'status
(let ((old-path (buffer-file-name))
(new-path (expand-file-name new-path)))
(when-let (dest (doom--copy-file old-path new-path force-p))
(when (file-exists-p old-path)
(delete-file old-path))
(kill-current-buffer)
(doom--forget-file old-path new-path)
(doom--update-file new-path)
(find-file new-path)
(message "File successfully moved to %s" dest))))
(`overwrite-self (error "Cannot overwrite self"))
(`aborted (message "Aborted"))
(_ t)))
(unless (and buffer-file-name (file-exists-p buffer-file-name))
(user-error "Buffer is not visiting any file"))
(let ((old-path (buffer-file-name (buffer-base-buffer)))
(new-path (expand-file-name new-path)))
(make-directory (file-name-directory new-path) 't)
(rename-file old-path new-path (or force-p 1))
(set-visited-file-name new-path t t)
(doom--update-files old-path new-path)
(message "File moved to %S" (abbreviate-file-name new-path))))
(defun doom--sudo-file (file)
(defun doom--sudo-file-path (file)
(let ((host (or (file-remote-p file 'host) "localhost")))
(concat "/" (when (file-remote-p file)
(concat (file-remote-p file 'method) ":"
@@ -334,10 +308,32 @@ file if it exists, without confirmation."
(defun doom/sudo-find-file (file)
"Open FILE as root."
(interactive "FOpen file as root: ")
(find-file (doom--sudo-file file)))
(find-file (doom--sudo-file-path file)))
;;;###autoload
(defun doom/sudo-this-file ()
"Open the current file as root."
(interactive)
(find-alternate-file (doom--sudo-file buffer-file-name)))
(find-file
(doom--sudo-file-path
(or buffer-file-name
(when (or (derived-mode-p 'dired-mode)
(derived-mode-p 'wdired-mode))
default-directory)))))
;;;###autoload
(defun doom/sudo-save-buffer ()
"Save this file as root."
(interactive)
(let ((file (doom--sudo-file-path buffer-file-name)))
(if-let (buffer (find-file-noselect file))
(let ((origin (current-buffer)))
(copy-to-buffer buffer (point-min) (point-max))
(unwind-protect
(with-current-buffer buffer
(save-buffer))
(unless (eq origin buffer)
(kill-buffer buffer))
(with-current-buffer origin
(revert-buffer t t))))
(user-error "Unable to open %S" file))))

View File

@@ -12,7 +12,7 @@ scaled up by `doom-big-font-increment'. See `doom-font' for details on
acceptable values for this variable.")
;;;###autoload
(defvar doom-big-font-increment 8
(defvar doom-big-font-increment 4
"How many steps to increase the font size (with `doom-font' as the base) when
`doom-big-font-mode' is enabled and `doom-big-font' is nil.")
@@ -20,56 +20,47 @@ acceptable values for this variable.")
;;
;;; Library
(defun doom--font-name (fontname frame)
(defun doom--font-name (fontname)
(when (query-fontset fontname)
(when-let (ascii (assq 'ascii (aref (fontset-info fontname frame) 2)))
(when-let (ascii (assq 'ascii (aref (fontset-info fontname) 2)))
(setq fontname (nth 2 ascii))))
(or (x-decompose-font-name fontname)
(error "Cannot decompose font name")))
(defun doom--frame-list (&optional frame)
"Return a list consisting of FRAME and all of FRAME's child frames."
(let ((frame (or frame (selected-frame))))
(cons (selected-frame)
(cl-loop for fr in (frame-list)
if (eq (frame-parameter fr 'parent-frame) frame)
collect fr))))
(defvar doom--font-scale nil)
;;;###autoload
(defun doom-adjust-font-size (increment &optional frame)
(defun doom-adjust-font-size (increment)
"Increase size of font in FRAME by INCREMENT.
FRAME parameter defaults to current frame."
(if (null increment)
(let ((frames (doom--frame-list frame)))
(dolist (frame frames)
(when (frame-parameter frame 'font-scale)
(set-frame-parameter frame 'font-scale nil)))
(set-frame-font doom-font 'keep-size frames)
(and frames t))
(let (success)
(dolist (frame (doom--frame-list frame))
(let* ((font (frame-parameter frame 'font))
(font (doom--font-name font frame))
(increment (* increment doom-font-increment))
(zoom-factor (or (frame-parameter frame 'font-scale) 0)))
(let ((new-size (+ (string-to-number (aref font xlfd-regexp-pixelsize-subnum))
increment)))
(unless (> new-size 0)
(error "Font is too small at %d" new-size))
(aset font xlfd-regexp-pixelsize-subnum (number-to-string new-size)))
;; Set point size & width to "*", so frame width will adjust to new font size
(aset font xlfd-regexp-pointsize-subnum "*")
(aset font xlfd-regexp-avgwidth-subnum "*")
(setq font (x-compose-font-name font))
(unless (x-list-fonts font)
(error "Cannot change font size"))
(set-frame-parameter frame 'font font)
(set-frame-parameter frame 'font-scale (+ zoom-factor increment))
(setq success t)))
(when success
;; Unlike `set-frame-font', `set-frame-parameter' won't trigger this
(run-hooks 'after-setting-font-hook)
t))))
(progn
(set-frame-font doom-font 'keep-size t)
(setf (alist-get 'font default-frame-alist)
(cond ((stringp doom-font) doom-font)
((fontp doom-font) (font-xlfd-name doom-font))
((signal 'wrong-type-argument (list '(fontp stringp)
doom-font)))))
t)
(let* ((font (frame-parameter nil 'font))
(font (doom--font-name font))
(increment (* increment doom-font-increment))
(zoom-factor (or doom--font-scale 0)))
(let ((new-size (+ (string-to-number (aref font xlfd-regexp-pixelsize-subnum))
increment)))
(unless (> new-size 0)
(error "Font is too small at %d" new-size))
(aset font xlfd-regexp-pixelsize-subnum (number-to-string new-size)))
;; Set point size & width to "*", so frame width will adjust to new font size
(aset font xlfd-regexp-pointsize-subnum "*")
(aset font xlfd-regexp-avgwidth-subnum "*")
(setq font (x-compose-font-name font))
(unless (x-list-fonts font)
(error "Cannot change font size"))
(set-frame-font font 'keep-size t)
(setf (alist-get 'font default-frame-alist) font)
(setq doom--font-scale (+ zoom-factor increment))
;; Unlike `set-frame-font', `set-frame-parameter' won't trigger this
(run-hooks 'after-setting-font-hook))))
;;
@@ -127,8 +118,13 @@ This uses `doom/increase-font-size' under the hood, and enlargens the font by
(unless doom-font
(user-error "`doom-font' must be set to a valid font"))
(if doom-big-font
(set-frame-font (if doom-big-font-mode doom-big-font doom-font)
'keep-size (doom--frame-list))
(let ((font (if doom-big-font-mode doom-big-font doom-font)))
(set-frame-font font 'keep-size t)
(setf (alist-get 'font default-frame-alist)
(cond ((stringp doom-font) font)
((fontp font) (font-xlfd-name font))
((signal 'wrong-type-argument (list '(fontp stringp)
font))))))
(doom-adjust-font-size
(and doom-big-font-mode
(integerp doom-big-font-increment)

View File

@@ -1,243 +0,0 @@
;;; core/autoload/format.el -*- lexical-binding: t; -*-
(defvar doom-format-ansi-alist
'(;; fx
(bold 1 :weight bold)
(dark 2)
(italic 3 :slant italic)
(underscore 4 :underline t)
(blink 5)
(rapid 6)
(contrary 7)
(concealed 8)
(strike 9 :strike-through t)
;; fg
(black 30 term-color-black)
(red 31 term-color-red)
(green 32 term-color-green)
(yellow 33 term-color-yellow)
(blue 34 term-color-blue)
(magenta 35 term-color-magenta)
(cyan 36 term-color-cyan)
(white 37 term-color-white)
;; bg
(on-black 40 term-color-black)
(on-red 41 term-color-red)
(on-green 42 term-color-green)
(on-yellow 43 term-color-yellow)
(on-blue 44 term-color-blue)
(on-magenta 45 term-color-magenta)
(on-cyan 46 term-color-cyan)
(on-white 47 term-color-white))
"An alist of fg/bg/fx names mapped to ansi codes and term-color-* variables.
This serves as the cipher for converting (COLOR ...) function calls in `print!'
and `format!' into colored output, where COLOR is any car of this list.")
(defvar doom-format-class-alist
`((color . doom--format-color)
(class . doom--format-class)
(indent . doom--format-indent)
(autofill . doom--format-autofill)
(success . (lambda (str &rest args)
(apply #'doom--format-color 'green (format "✓ %s" str) args)))
(warn . (lambda (str &rest args)
(apply #'doom--format-color 'yellow (format "! %s" str) args)))
(error . (lambda (str &rest args)
(apply #'doom--format-color 'red (format "x %s" str) args)))
(info . (lambda (str &rest args)
(concat "- " (if args (apply #'format str args) str))))
(start . (lambda (str &rest args)
(concat "> " (if args (apply #'format str args) str))))
(debug . (lambda (str &rest args)
(if doom-debug-mode
(if args
(apply #'format str args)
(format "%s" str))
"")))
(path . abbreviate-file-name)
(symbol . symbol-name)
(relpath . (lambda (str &optional dir)
(if (or (not str)
(not (stringp str))
(string-empty-p str))
str
(let ((dir (or dir (file-truename default-directory)))
(str (file-truename str)))
(if (file-in-directory-p str dir)
(file-relative-name str dir)
(abbreviate-file-name str))))))
(filename . file-name-nondirectory)
(dirname . (lambda (path)
(unless (file-directory-p path)
(setq path (file-name-directory path)))
(directory-file-name path))))
"An alist of text classes that map to transformation functions.
Any of these classes can be called like functions from within `format!' and
`print!' calls, which will transform their input.")
(defvar doom-format-indent 0
"Level to rigidly indent text returned by `format!' and `print!'.")
(defvar doom-format-indent-increment 2
"Steps in which to increment `doom-format-indent' for consecutive levels.")
(defvar doom-format-backend
(if noninteractive 'ansi 'text-properties)
"Determines whether to print colors with ANSI codes or with text properties.
Accepts 'ansi and 'text-properties. nil means don't render colors.")
;;
;;; Library
;;;###autoload
(defun doom--format (output)
(if (string-empty-p (string-trim output))
""
(concat (make-string doom-format-indent 32)
(replace-regexp-in-string
"\n" (concat "\n" (make-string doom-format-indent 32))
output t t))))
;;;###autoload
(defun doom--format-print (output)
(unless (string-empty-p output)
(princ output)
(when (or noninteractive (not (eq standard-output t)))
(terpri)) ; newline
t))
;;;###autoload
(defun doom--format-indent (width text &optional prefix)
"Indent TEXT by WIDTH spaces. If ARGS, format TEXT with them."
(with-temp-buffer
(setq text (format "%s" text))
(insert text)
(indent-rigidly (point-min) (point-max) width)
(when (stringp prefix)
(when (> width 2)
(goto-char (point-min))
(beginning-of-line-text)
(delete-char (- (length prefix)))
(insert prefix)))
(buffer-string)))
;;;###autoload
(defun doom--format-autofill (&rest msgs)
"Ensure MSG is split into lines no longer than `fill-column'."
(with-temp-buffer
(let ((fill-column 76))
(dolist (line msgs)
(when line
(insert (format "%s" line))))
(fill-region (point-min) (point-max))
(buffer-string))))
;;;###autoload
(defun doom--format-color (style format &rest args)
"Apply STYLE to formatted MESSAGE with ARGS.
STYLE is a symbol that correlates to `doom-format-ansi-alist'.
In a noninteractive session, this wraps the result in ansi color codes.
Otherwise, it maps colors to a term-color-* face."
(let* ((code (cadr (assq style doom-format-ansi-alist)))
(format (format "%s" format))
(message (if args (apply #'format format args) format)))
(unless code
(error "%S is an invalid color" style))
(pcase doom-format-backend
(`ansi
(format "\e[%dm%s\e[%dm" code message 0))
(`text-properties
(require 'term) ; piggyback on term's color faces
(propertize
message
'face
(append (get-text-property 0 'face format)
(cond ((>= code 40)
`(:background ,(caddr (assq style doom-format-ansi-alist))))
((>= code 30)
`(:foreground ,(face-foreground (caddr (assq style doom-format-ansi-alist)))))
((cddr (assq style doom-format-ansi-alist)))))))
(_ message))))
;;;###autoload
(defun doom--format-class (class format &rest args)
"Apply CLASS to formatted format with ARGS.
CLASS is derived from `doom-format-class-alist', and can contain any arbitrary,
transformative logic."
(let (fn)
(cond ((setq fn (cdr (assq class doom-format-class-alist)))
(if (functionp fn)
(apply fn format args)
(error "%s does not have a function" class)))
(args (apply #'format format args))
(format))))
;;;###autoload
(defun doom--format-apply (forms &optional sub)
"Replace color-name functions with calls to `doom--format-color'."
(cond ((null forms) nil)
((listp forms)
(append (cond ((not (symbolp (car forms)))
(list (doom--format-apply (car forms))))
(sub
(list (car forms)))
((assq (car forms) doom-format-ansi-alist)
`(doom--format-color ',(car forms)))
((assq (car forms) doom-format-class-alist)
`(doom--format-class ',(car forms)))
((list (car forms))))
(doom--format-apply (cdr forms) t)
nil))
(forms)))
;;;###autoload
(defmacro format! (message &rest args)
"An alternative to `format' that understands (color ...) and converts them
into faces or ANSI codes depending on the type of sesssion we're in."
`(doom--format (format ,@(doom--format-apply `(,message ,@args)))))
;;;###autoload
(defmacro print-group! (&rest body)
"Indents any `print!' or `format!' output within BODY."
`(let ((doom-format-indent (+ doom-format-indent-increment doom-format-indent)))
,@body))
;;;###autoload
(defmacro print! (message &rest args)
"Uses `message' in interactive sessions and `princ' otherwise (prints to
standard out).
Can be colored using (color ...) blocks:
(print! \"Hello %s\" (bold (blue \"How are you?\")))
(print! \"Hello %s\" (red \"World\"))
(print! (green \"Great %s!\") \"success\")
Uses faces in interactive sessions and ANSI codes otherwise."
`(doom--format-print (format! ,message ,@args)))
;;;###autoload
(defmacro insert! (message &rest args)
"Like `insert'; the last argument must be format arguments for MESSAGE.
\(fn MESSAGE... ARGS)"
`(insert (format! (concat ,message ,@(butlast args))
,@(car (last args)))))
;;;###autoload
(defmacro error! (message &rest args)
"Like `error', but with the power of `format!'."
`(error (format! ,message ,@args)))
;;;###autoload
(defmacro user-error! (message &rest args)
"Like `user-error', but with the power of `format!'."
`(user-error (format! ,message ,@args)))

View File

@@ -3,9 +3,6 @@
(defvar doom--help-major-mode-module-alist
'((dockerfile-mode :tools docker)
(agda2-mode :lang agda)
(haxor-mode :lang assembly)
(mips-mode :lang assembly)
(nasm-mode :lang assembly)
(c-mode :lang cc)
(c++-mode :lang cc)
(objc++-mode :lang cc)
@@ -34,6 +31,7 @@
(js2-mode :lang javascript)
(rjsx-mode :lang javascript)
(typescript-mode :lang javascript)
(typescript-tsx-mode :lang javascript)
(coffee-mode :lang javascript)
(julia-mode :lang julia)
(kotlin-mode :lang kotlin)
@@ -41,13 +39,14 @@
(LaTeX-mode :lang latex)
(ledger-mode :lang ledger)
(lua-mode :lang lua)
(moonscript-mode :lang lua)
(markdown-mode :lang markdown)
(gfm-mode :lang markdown)
(nim-mode :lang nim)
(nix-mode :lang nix)
(taureg-mode :lang ocaml)
(org-mode :lang org)
(perl-mode :lang perl)
(raku-mode :lang raku)
(php-mode :lang php)
(hack-mode :lang php)
(plantuml-mode :lang plantuml)
@@ -55,9 +54,10 @@
(python-mode :lang python)
(restclient-mode :lang rest)
(ruby-mode :lang ruby)
(enh-ruby-mode :lang ruby)
(rust-mode :lang rust)
(rustic-mode :lang rust)
(scala-mode :lang scala)
(scheme-mode :lang scheme)
(sh-mode :lang sh)
(swift-mode :lang swift)
(web-mode :lang web)
@@ -96,7 +96,7 @@ the current major-modea.")
"Get information on an active minor mode. Use `describe-minor-mode' for a
selection of all minor-modes, active or not."
(interactive
(list (completing-read "Minor mode: " (doom-active-minor-modes))))
(list (completing-read "Describe active mode: " (doom-active-minor-modes))))
(let ((symbol
(cond ((stringp mode) (intern mode))
((symbolp mode) mode)
@@ -105,21 +105,6 @@ selection of all minor-modes, active or not."
(helpful-function symbol)
(helpful-variable symbol))))
;;;###autoload
(defun doom/describe-symbol (symbol)
"Show help for SYMBOL, a variable, function or macro."
(interactive
(list (helpful--read-symbol "Symbol: " #'helpful--bound-p)))
(let* ((sym (intern-soft symbol))
(bound (boundp sym))
(fbound (fboundp sym)))
(cond ((and sym bound (not fbound))
(helpful-variable sym))
((and sym fbound (not bound))
(helpful-callable sym))
((apropos (format "^%s\$" symbol)))
((apropos (format "%s" symbol))))))
;;
;;; Documentation commands
@@ -130,7 +115,8 @@ selection of all minor-modes, active or not."
(require 'org)
(let* ((default-directory doom-docs-dir)
(org-agenda-files (mapcar #'expand-file-name (doom-enlist files)))
(depth (if (integerp depth) depth)))
(depth (if (integerp depth) depth))
(org-inhibit-startup t))
(message "Loading search results...")
(unwind-protect
(delq
@@ -150,7 +136,8 @@ selection of all minor-modes, active or not."
(list (or (+org-get-global-property "TITLE")
(file-relative-name (buffer-file-name)))))
path
(list (replace-regexp-in-string org-link-any-re "\\4" text)))
(when text
(list (replace-regexp-in-string org-link-any-re "\\4" text))))
" > ")
tags)
" ")
@@ -196,7 +183,7 @@ selection of all minor-modes, active or not."
(find-file (expand-file-name "index.org" doom-docs-dir)))
;;;###autoload
(defun doom/help-search (&optional initial-input)
(defun doom/help-search-headings (&optional initial-input)
"Search Doom's documentation and jump to a headline."
(interactive)
(doom-completing-read-org-headings
@@ -213,7 +200,26 @@ selection of all minor-modes, active or not."
(doom--help-modules-list))))
;;;###autoload
(defun doom/help-news-search (&optional initial-input)
(defun doom/help-search (&optional initial-input)
"Preform a text search on all of Doom's documentation."
(interactive)
(funcall (cond ((fboundp '+ivy-file-search)
#'+ivy-file-search)
((fboundp '+helm-file-search)
#'+helm-file-search)
((rgrep
(read-regexp
"Search for" (or initial-input 'grep-tag-default)
'grep-regexp-history)
"*.org" doom-emacs-dir)
#'ignore))
:query initial-input
:args '("-g" "*.org")
:in doom-emacs-dir
:prompt "Search documentation for: "))
;;;###autoload
(defun doom/help-search-news (&optional initial-input)
"Search headlines in Doom's newsletters."
(interactive)
(doom-completing-read-org-headings
@@ -316,13 +322,7 @@ without needing to check if they are available."
readme-path)))
(defun doom--help-current-module-str ()
(cond ((and buffer-file-name
(eq major-mode 'emacs-lisp-mode)
(file-in-directory-p buffer-file-name doom-private-dir)
(save-excursion (goto-char (point-min))
(re-search-forward "^\\s-*(doom! " nil t))
(thing-at-point 'sexp t)))
((save-excursion
(cond ((save-excursion
(require 'smartparens)
(ignore-errors
(sp-beginning-of-sexp)
@@ -341,11 +341,14 @@ without needing to check if they are available."
(symbol-name (cadr mod)))))))
;;;###autoload
(defun doom/help-modules (category module)
(defun doom/help-modules (category module &optional visit-dir)
"Open the documentation for a Doom module.
CATEGORY is a keyword and MODULE is a symbol. e.g. :editor and 'evil.
If VISIT-DIR is non-nil, visit the module's directory rather than its
documentation.
Automatically selects a) the module at point (in private init files), b) the
module derived from a `featurep!' or `require!' call, c) the module that the
current file is in, or d) the module associated with the current major mode (see
@@ -354,7 +357,8 @@ current file is in, or d) the module associated with the current major mode (see
(mapcar #'intern
(split-string
(completing-read "Describe module: "
(doom--help-modules-list) nil t nil nil
(doom--help-modules-list)
nil t nil nil
(doom--help-current-module-str))
" " t)))
(cl-check-type category symbol)
@@ -362,14 +366,19 @@ current file is in, or d) the module associated with the current major mode (see
(cl-destructuring-bind (module-string path)
(or (assoc (format "%s %s" category module) (doom--help-modules-list))
(user-error "'%s %s' is not a valid module" category module))
(setq module-string (substring-no-properties module-string))
(unless (file-readable-p path)
(error "Can't find or read %S module at %S" module-string path))
(if (not (file-directory-p path))
(find-file path)
(if (y-or-n-p (format "The %S module has no README file. Explore its directory?"
module-string))
(doom-project-browse path)
(user-error "Aborted module lookup")))))
(cond ((not (file-directory-p path))
(if visit-dir
(doom-project-browse (file-name-directory path))
(find-file path)))
(visit-dir
(doom-project-browse path))
((y-or-n-p (format "The %S module has no README file. Explore its directory?"
module-string))
(doom-project-browse (file-name-directory path)))
((user-error "Aborted module lookup")))))
;;
@@ -419,8 +428,8 @@ If prefix arg is present, refresh the cache."
(let ((guess (or (function-called-at-point)
(symbol-at-point))))
(require 'finder-inf nil t)
(require 'core-packages)
(doom-initialize-packages)
(require 'package)
(require 'straight)
(let ((packages (delete-dups
(append (mapcar #'car package-alist)
(mapcar #'car package--builtins)
@@ -432,9 +441,9 @@ If prefix arg is present, refresh the cache."
(list
(intern
(completing-read (if guess
(format "Select package to search for (default %s): "
(format "Select Doom package to search for (default %s): "
guess)
"Describe package: ")
"Describe Doom package: ")
packages nil t nil nil
(if guess (symbol-name guess))))))))
(require 'core-packages)
@@ -462,27 +471,44 @@ If prefix arg is present, refresh the cache."
(insert (symbol-name package) "\n")
(package--print-help-section "Source")
(insert (or (pcase (doom-package-backend package)
(`straight
(format! "Straight (%s)\n%s"
(let ((default-directory (straight--build-dir (symbol-name package))))
(cdr
(doom-call-process "git" "log" "-1" "--format=%D %h %ci")))
(indent
13 (string-trim
(pp-to-string
(doom-package-build-recipe package))))))
(`elpa
(format "[M]ELPA %s" (doom--package-url package)))
(`builtin "Built-in")
(_ (abbreviate-file-name (symbol-file package))))
"unknown")
"\n")
(pcase (doom-package-backend package)
(`straight
(insert "Straight\n")
(package--print-help-section "Pinned")
(insert (if-let (pin (plist-get (cdr (assq package doom-packages)) :pin))
pin
"unpinned")
"\n")
(package--print-help-section "Build")
(insert (let ((default-directory (straight--repos-dir (symbol-name package))))
(cdr
(doom-call-process "git" "log" "-1" "--format=%D %h %ci")))
"\n")
(let ((recipe (doom-package-build-recipe package)))
(insert (format! "%s\n"
(indent 13
(string-trim (pp-to-string recipe)))))
(when (gethash (symbol-name package) straight--build-cache)
(package--print-help-section "Homepage")
(insert (doom--package-url package))))
(`elpa (insert "[M]ELPA " (doom--package-url package)))
(`builtin (insert "Built-in"))
(`other (insert
(abbreviate-file-name
(or (symbol-file package)
(locate-library (symbol-name package))))))
(_ (insert "Not installed")))
(insert "\n")
(when-let
(modules
(if (gethash (symbol-name package) straight--build-cache)
(doom-package-get package :modules)
(plist-get (cdr (assq package (doom-package-list 'all)))
:modules)))
(package--print-help-section "Modules")
(insert "Declared by the following Doom modules:\n")
(dolist (m (doom-package-get package :modules))
(dolist (m modules)
(insert indent)
(doom--help-package-insert-button
(format "%s %s" (car m) (or (cdr m) ""))
@@ -512,7 +538,7 @@ If prefix arg is present, refresh the cache."
(insert "\n\n")))))
(defvar doom--package-cache nil)
(defun doom--package-list ()
(defun doom--package-list (&optional prompt)
(let* ((guess (or (function-called-at-point)
(symbol-at-point))))
(require 'finder-inf nil t)
@@ -528,10 +554,11 @@ If prefix arg is present, refresh the cache."
(setq doom--package-cache packages)
(unless (memq guess packages)
(setq guess nil))
(intern (completing-read (if guess
(format "Select package to search for (default %s): "
guess)
"Describe package: ")
(intern (completing-read (or prompt
(if guess
(format "Select package to search for (default %s): "
guess)
"Describe package: "))
packages nil t nil nil
(if guess (symbol-name guess)))))))
@@ -578,7 +605,7 @@ If prefix arg is present, refresh the cache."
This only searches `doom-emacs-dir' (typically ~/.emacs.d) and does not include
config blocks in your private config."
(interactive (list (doom--package-list)))
(interactive (list (doom--package-list "Find package config: ")))
(cl-destructuring-bind (file line _match)
(split-string
(completing-read
@@ -592,55 +619,48 @@ config blocks in your private config."
(recenter)))
;;;###autoload
(defun doom/help-package-homepage (package)
"Open PACKAGE's repo or homepage in your browser."
(interactive (list (doom--package-list)))
(browse-url (doom--package-url package)))
(defalias 'doom/help-package-homepage #'straight-visit-package-website)
(defun doom--help-search-prompt (prompt)
(let ((query (doom-thing-at-point-or-region)))
(if (featurep 'counsel)
query
(read-string prompt query 'git-grep query))))
(defvar counsel-rg-base-command)
(defun doom--help-search (dirs query prompt)
;; REVIEW Replace with deadgrep
(unless (executable-find "rg")
(user-error "Can't find ripgrep on your system"))
(if (fboundp 'counsel-rg)
(let ((counsel-rg-base-command (append counsel-rg-base-command dirs)))
(counsel-rg query nil "-Lz" prompt))
;; TODO Add helm support?
(grep-find
(string-join
(append (list "rg" "-L" "--search-zip" "--no-heading" "--color=never"
(shell-quote-argument query))
(mapcar #'shell-quote-argument dirs))
" "))))
;;;###autoload
(defun doom/help-search-load-path (query)
"Perform a text search on your `load-path'.
Uses the symbol at point or the current selection, if available."
(interactive
(let ((query
;; TODO Generalize this later; into something the lookup module and
;; project search commands could as well
(if (use-region-p)
(buffer-substring-no-properties (region-beginning) (region-end))
(or (symbol-name (symbol-at-point)) ""))))
(list (read-string
(format "Search load-path (default: %s): " query)
nil 'git-grep query))))
;; REVIEW Replace with deadgrep
(grep-find
(mapconcat
#'shell-quote-argument
(append (list "rg" "-L" "--search-zip" "--no-heading" "--color=never" query)
(cl-remove-if-not #'file-directory-p load-path))
" ")))
(list (doom--help-search-prompt "Search load-path: ")))
(doom--help-search (cl-remove-if-not #'file-directory-p load-path)
query "Search load-path: "))
;; TODO factor our the duplicate code between this and the above
;;;###autoload
(defun doom/help-search-loaded-files (query)
"Perform a text search on your `load-path'.
Uses the symbol at point or the current selection, if available."
(interactive
(let ((query
;; TODO Generalize this later; into something the lookup module and
;; project search commands could as well.
(if (use-region-p)
(buffer-substring-no-properties (region-beginning) (region-end))
(or (symbol-name (symbol-at-point)) ""))))
(list (read-string
(format "Search load-path (default: %s): " query)
nil 'git-grep query))))
(unless (executable-find "rg")
(user-error "Can't find ripgrep on your system"))
(require 'elisp-refs)
;; REVIEW Replace with deadgrep
(grep-find
(mapconcat
#'shell-quote-argument
(append (list "rg" "-L" "--search-zip" "--no-heading" "--color=never" query)
(cl-remove-if-not #'file-directory-p (elisp-refs--loaded-paths)))
" ")))
(list (doom--help-search-prompt "Search loaded files: ")))
(doom--help-search
(cl-loop for (file . _) in (cl-remove-if-not #'stringp load-history :key #'car)
for filebase = (file-name-sans-extension file)
if (file-exists-p! (format "%s.el" filebase))
collect it)
query "Search loaded files: "))

View File

@@ -1,198 +1,5 @@
;;; core/autoload/packages.el -*- lexical-binding: t; -*-
;;
;;; Package metadata
;;;###autoload
(defun doom-package-get (package &optional prop nil-value)
"Returns PACKAGE's `package!' recipe from `doom-packages'."
(let ((plist (cdr (assq package doom-packages))))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
;;;###autoload
(defun doom-package-recipe (package &optional prop nil-value)
"Returns the `straight' recipe PACKAGE was registered with."
(let ((plist (gethash (symbol-name package) straight--recipe-cache)))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
;;;###autoload
(defun doom-package-build-recipe (package &optional prop nil-value)
"Returns the `straight' recipe PACKAGE was installed with."
(let ((plist (nth 2 (gethash (symbol-name package) straight--build-cache))))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
;;;###autoload
(defun doom-package-build-time (package)
"TODO"
(car (gethash (symbol-name package) straight--build-cache)))
;;;###autoload
(defun doom-package-dependencies (package &optional recursive noerror)
"Return a list of dependencies for a package."
(let ((deps (nth 1 (gethash (symbol-name package) straight--build-cache))))
(if recursive
(nconc deps (mapcan (lambda (dep) (doom-package-dependencies dep t t))
deps))
deps)))
(defun doom-package-depending-on (package &optional noerror)
"Return a list of packages that depend on the package named NAME."
(cl-check-type name symbol)
;; can't get dependencies for built-in packages
(unless (or (doom-package-build-recipe name)
noerror)
(error "Couldn't find %s, is it installed?" name))
(cl-loop for pkg in (hash-table-keys straight--build-cache)
for deps = (doom-package-dependencies pkg)
if (memq package deps)
collect pkg
and append (doom-package-depending-on pkg t)))
;;
;;; Predicate functions
;;;###autoload
(defun doom-package-built-in-p (package)
"Return non-nil if PACKAGE (a symbol) is built-in."
(eq (doom-package-build-recipe package :type)
'built-in))
;;;###autoload
(defun doom-package-installed-p (package)
"Return non-nil if PACKAGE (a symbol) is installed."
(file-directory-p (straight--build-dir (symbol-name package))))
;;;###autoload
(defun doom-package-registered-p (package)
"Return non-nil if PACKAGE (a symbol) has been registered with `package!'.
Excludes packages that have a non-nil :built-in property."
(when-let (plist (doom-package-get package))
(not (plist-get plist :ignore))))
;;;###autoload
(defun doom-package-private-p (package)
"Return non-nil if PACKAGE was installed by the user's private config."
(assq :private (doom-package-get package :modules)))
;;;###autoload
(defun doom-package-protected-p (package)
"Return non-nil if PACKAGE is protected.
A protected package cannot be deleted and will be auto-installed if missing."
(memq package doom-core-packages))
;;;###autoload
(defun doom-package-core-p (package)
"Return non-nil if PACKAGE is a core Doom package."
(or (doom-package-protected-p package)
(assq :core (doom-package-get package :modules))))
;;;###autoload
(defun doom-package-backend (package)
"Return 'straight, 'builtin, 'elpa or 'other, depending on how PACKAGE is
installed."
(cond ((gethash (symbol-name package) straight--build-cache)
'straight)
((or (doom-package-built-in-p package)
(assq package package--builtins))
'builtin)
((assq package package-alist)
'elpa)
('other)))
;;;###autoload
(defun doom-package-different-recipe-p (name)
"Return t if a package named NAME (a symbol) has a different recipe than it
was installed with."
(cl-check-type name symbol)
;; TODO
;; (when (doom-package-installed-p name)
;; (when-let* ((doom-recipe (assq name doom-packages))
;; (install-recipe (doom-package-recipe)))
;; (not (equal (cdr quelpa-recipe)
;; (cdr (plist-get (cdr doom-recipe) :recipe))))))
)
;;
;;; Package list getters
(defun doom--read-module-packages-file (file &optional noeval noerror)
(with-temp-buffer ; prevent buffer-local settings from propagating
(condition-case e
(if (not noeval)
(load file noerror t t)
(when (file-readable-p file)
(insert-file-contents file)
(delay-mode-hooks (emacs-lisp-mode))
(while (search-forward "(package! " nil t)
(save-excursion
(goto-char (match-beginning 0))
(unless (let ((ppss (syntax-ppss)))
(or (nth 3 ppss)
(nth 4 ppss)))
(cl-destructuring-bind (name . plist)
(cdr (sexp-at-point))
(push (cons
name (plist-put
plist :modules
(list (doom-module-from-path file))))
doom-packages)))))))
((debug error)
(signal 'doom-package-error
(list (doom-module-from-path file)
e))))))
;;;###autoload
(defun doom-package-list (&optional all-p)
"Retrieve a list of explicitly declared packages from enabled modules.
This excludes core packages listed in `doom-core-packages'.
If ALL-P, gather packages unconditionally across all modules, including disabled
ones."
(let ((doom-interactive-mode t)
(doom-modules (doom-modules))
doom-packages
doom-disabled-packages)
(doom--read-module-packages-file
(doom-path doom-core-dir "packages.el") all-p t)
(let ((private-packages (doom-path doom-private-dir "packages.el")))
(unless all-p
;; We load the private packages file twice to ensure disabled packages
;; are seen ASAP, and a second time to ensure privately overridden
;; packages are properly overwritten.
(doom--read-module-packages-file private-packages nil t))
(if all-p
(mapc #'doom--read-module-packages-file
(doom-files-in doom-modules-dir
:depth 2
:match "/packages\\.el$"))
(cl-loop for key being the hash-keys of doom-modules
for path = (doom-module-path (car key) (cdr key) "packages.el")
for doom--current-module = key
do (doom--read-module-packages-file path nil t)))
(doom--read-module-packages-file private-packages all-p t))
(nreverse doom-packages)))
;;
;;; Main functions
;;;###autoload
(defun doom/reload-packages ()
"Reload `doom-packages', `package' and `quelpa'."
@@ -201,3 +8,190 @@ ones."
(message "Reloading packages")
(doom-initialize-packages t)
(message "Reloading packages...DONE"))
;;
;;; Bump commands
(defun doom--package-full-recipe (package plist)
(doom-plist-merge
(plist-get plist :recipe)
(or (cdr (straight-recipes-retrieve package))
(plist-get (cdr (assq package doom-packages))
:recipe))))
(defun doom--package-to-bump-string (package plist)
"Return a PACKAGE and its PLIST in 'username/repo@commit' format."
(format "%s@%s"
(plist-get (doom--package-full-recipe package plist) :repo)
(substring-no-properties (plist-get plist :pin) 0 7)))
(defun doom--package-at-point (&optional point)
"Return the package and plist from the (package! PACKAGE PLIST...) at point."
(save-match-data
(save-excursion
(and point (goto-char point))
(while (and (or (atom (sexp-at-point))
(doom-point-in-string-or-comment-p))
(search-backward "(" nil t)))
(when (eq (car-safe (sexp-at-point)) 'package!)
(cl-destructuring-bind (beg . end)
(bounds-of-thing-at-point 'sexp)
(let* ((doom-packages nil)
(buffer-file-name
(or buffer-file-name
(bound-and-true-p org-src-source-file-name)))
(package (eval (sexp-at-point) t)))
(list :beg beg
:end end
:package (car package)
:plist (cdr package))))))))
;;;###autoload
(defun doom/bumpify-package-at-point ()
"Convert `package!' call at point to a bump string."
(interactive)
(cl-destructuring-bind (&key package plist beg end)
(doom--package-at-point)
(when-let (str (doom--package-to-bump-string package plist))
(goto-char beg)
(delete-region beg end)
(insert str))))
;;;###autoload
(defun doom/bumpify-packages-in-buffer ()
"Convert all `package!' calls in buffer into bump strings."
(interactive)
(save-excursion
(goto-char (point-min))
(while (search-forward "(package!" nil t)
(unless (doom-point-in-string-or-comment-p)
(doom/bumpify-package-at-point)))))
;;;###autoload
(defun doom/bump-package-at-point (&optional select)
"Inserts or updates a `:pin' for the `package!' statement at point.
Grabs the latest commit id of the package using 'git'."
(interactive "P")
(doom-initialize-packages)
(cl-destructuring-bind (&key package plist beg end)
(or (doom--package-at-point)
(user-error "Not on a `package!' call"))
(let* ((recipe (doom--package-full-recipe package plist))
(branch (or (plist-get recipe :branch)
straight-vc-git-default-branch))
(oldid (or (plist-get plist :pin)
(doom-package-get package :pin)))
(url (straight-vc-git--destructure recipe (upstream-repo upstream-host)
(straight-vc-git--encode-url upstream-repo upstream-host)))
(id (or (when url
(cdr (doom-call-process
"git" "ls-remote" url
(unless select
(or branch straight-vc-git-default-branch)))))
(user-error "Couldn't find a recipe for %s" package)))
(id (car (split-string
(if select
(completing-read "Commit: " (split-string id "\n" t))
id)))))
(when (and oldid
(plist-member plist :pin)
(equal oldid id))
(user-error "%s: no update necessary" package))
(save-excursion
(if (re-search-forward ":pin +\"\\([^\"]+\\)\"" end t)
(replace-match id t t nil 1)
(goto-char (1- end))
(insert " :pin " (prin1-to-string id))))
(cond ((not oldid)
(message "%s: → %s" package (substring id 0 10)))
((< (length oldid) (length id))
(message "%s: extended to %s..." package id))
((message "%s: %s → %s"
package
(substring oldid 0 10)
(substring id 0 10)))))))
;;;###autoload
(defun doom/bump-packages-in-buffer (&optional select)
"Inserts or updates a `:pin' for the `package!' statement at point.
Grabs the latest commit id of the package using 'git'."
(interactive "P")
(save-excursion
(goto-char (point-min))
(doom-initialize-packages)
(let (packages)
(while (search-forward "(package! " nil t)
(unless (let ((ppss (syntax-ppss)))
(or (nth 4 ppss)
(nth 3 ppss)
(save-excursion
(and (goto-char (match-beginning 0))
(not (plist-member (sexp-at-point) :pin))))))
(condition-case e
(push (doom/bump-package-at-point) packages)
(user-error (message "%s" (error-message-string e))))))
(if packages
(message "Updated %d packages\n- %s" (length packages) (string-join packages "\n- "))
(message "No packages to update")))))
;;;###autoload
(defun doom/bump-module (category &optional module select)
"Bump packages in CATEGORY MODULE.
If SELECT (prefix arg) is non-nil, prompt you to choose a specific commit for
each package."
(interactive
(let* ((module (completing-read
"Bump module: "
(let ((modules (doom-module-list 'all)))
(mapcar (lambda (m)
(if (listp m)
(format "%s %s" (car m) (cdr m))
(format "%s" m)))
(append '(:private :core)
(delete-dups (mapcar #'car modules))
modules)))
nil t nil nil))
(module (split-string module " " t)))
(list (intern (car module))
(ignore-errors (intern (cadr module)))
current-prefix-arg)))
(mapc (fn! ((cat . mod))
(if-let (packages-file
(pcase cat
(:private (doom-glob doom-private-dir "packages.el"))
(:core (doom-glob doom-core-dir "packages.el"))
(_ (doom-module-locate-path cat mod "packages.el"))))
(with-current-buffer
(or (get-file-buffer packages-file)
(find-file-noselect packages-file))
(doom/bump-packages-in-buffer select)
(save-buffer))
(message "Module %s has no packages.el file" (cons cat mod))))
(if module
(list (cons category module))
(cl-remove-if-not (lambda (m) (eq (car m) category))
(append '((:core) (:private))
(doom-module-list 'all))))))
;;;###autoload
(defun doom/bump-package (package)
"Bump PACKAGE in all modules that install it."
(interactive
(list (completing-read "Bump package: "
(mapcar #'car (doom-package-list 'all)))))
(let* ((packages (doom-package-list 'all))
(modules (plist-get (alist-get package packages) :modules)))
(unless modules
(user-error "This package isn't installed by any Doom module"))
(dolist (module modules)
(when-let (packages-file (doom-module-locate-path (car module) (cdr module)))
(doom/bump-module (car module) (cdr module))))))
;;
;;; Bump commits
;;;###autoload
(defun doom/commit-bumps ()
(interactive))

View File

@@ -35,24 +35,6 @@ list, the pair is destructured into (CAR . CDR)."
"Delete PROP from PLIST in-place."
`(setq ,plist (doom-plist-delete ,plist ,prop)))
;;;###autoload
(defmacro with-plist! (plist props &rest body)
"With props bound from PLIST to PROPS, evaluate BODY.
PROPS is a list of symbols. Each one is converted to a keyword and then its
value is looked up in the PLIST and bound to the symbol for the duration of
BODY."
(declare (indent 2))
(let ((plist-sym (make-symbol "plist")))
`(let* ((,plist-sym ,plist)
,@(cl-loop for prop in props
collect
`(,prop
(plist-get
,plist-sym
,(doom-keyword-intern (symbol-name prop))))))
,@body)))
;;
;;; Library
@@ -66,7 +48,7 @@ BODY."
;;;###autoload
(defun doom-plist-merge (from-plist to-plist)
"Destructively merge FROM-PLIST onto TO-PLIST"
"Non-destructively merge FROM-PLIST onto TO-PLIST"
(let ((plist (copy-sequence from-plist)))
(while plist
(plist-put! to-plist (pop plist) (pop plist)))
@@ -83,11 +65,11 @@ BODY."
p))
;;;###autoload
(defun doom-plist-delete (plist prop)
"Delete PROP from a copy of PLIST."
(defun doom-plist-delete (plist &rest props)
"Delete PROPS from a copy of PLIST."
(let (p)
(while plist
(if (not (eq prop (car plist)))
(if (not (memq (car plist) props))
(plist-put! p (car plist) (nth 1 plist)))
(setq plist (cddr plist)))
p))

View File

@@ -1,6 +1,8 @@
;;; core/autoload/projects.el -*- lexical-binding: t; -*-
(defvar projectile-project-root nil)
(defvar projectile-enable-caching)
(defvar projectile-require-project-root)
;;;###autoload (autoload 'projectile-relevant-known-projects "projectile")
@@ -39,8 +41,7 @@ they are absolute."
"Preforms `projectile-find-file' in a known project of your choosing."
(interactive
(list
(completing-read "Find file in project: " (projectile-relevant-known-projects)
nil nil nil nil (doom-project-root))))
(completing-read "Find file in project: " (projectile-relevant-known-projects))))
(unless (file-directory-p project-root)
(error "Project directory '%s' doesn't exist" project-root))
(doom-project-find-file project-root))
@@ -50,8 +51,7 @@ they are absolute."
"Preforms `find-file' in a known project of your choosing."
(interactive
(list
(completing-read "Browse in project: " (projectile-relevant-known-projects)
nil nil nil nil (doom-project-root))))
(completing-read "Browse in project: " (projectile-relevant-known-projects))))
(unless (file-directory-p project-root)
(error "Project directory '%s' doesn't exist" project-root))
(doom-project-browse project-root))
@@ -99,11 +99,10 @@ If DIR is not a project, it will be indexed (but not cached)."
(unless (file-readable-p dir)
(error "Directory %S isn't readable" dir))
(let* ((default-directory (file-truename (expand-file-name dir)))
(project-root (doom-project-root default-directory))
(projectile-project-root default-directory)
(projectile-project-root (doom-project-root default-directory))
(projectile-enable-caching projectile-enable-caching))
(cond ((and project-root (file-equal-p project-root projectile-project-root))
(unless (doom-project-p projectile-project-root)
(cond ((and projectile-project-root (file-equal-p projectile-project-root default-directory))
(unless (doom-project-p default-directory)
;; Disable caching if this is not a real project; caching
;; non-projects easily has the potential to inflate the projectile
;; cache beyond reason.

View File

@@ -8,9 +8,11 @@ Will be saved in `doom-scratch-dir'.")
(defvar doom-scratch-dir (concat doom-etc-dir "scratch")
"Where to save persistent scratch buffers.")
(defvar doom-scratch-buffer-major-mode nil
"What major mode to use in scratch buffers. This can be one of the
following:
(defvar doom-scratch-initial-major-mode nil
"What major mode to start fresh scratch buffers in.
Scratch buffers preserve their last major mode, however, so this only affects
the first, fresh scratch buffer you create. This accepts:
t Inherits the major mode of the last buffer you had selected.
nil Uses `fundamental-mode'
@@ -21,43 +23,56 @@ following:
(defvar doom-scratch-current-project nil
"The name of the project associated with the current scratch buffer.")
(put 'doom-scratch-current-project 'permanent-local t)
(defvar doom-scratch-buffer-hook ()
"The hooks to run after a scratch buffer is created.")
(defun doom--load-persistent-scratch-buffer (name)
(defun doom--load-persistent-scratch-buffer (project-name)
(setq-local doom-scratch-current-project
(or name
(or project-name
doom-scratch-default-file))
(let ((scratch-file
(expand-file-name doom-scratch-current-project
(let ((smart-scratch-file
(expand-file-name (concat doom-scratch-current-project ".el")
doom-scratch-dir)))
(make-directory doom-scratch-dir t)
(when (file-readable-p scratch-file)
(erase-buffer)
(insert-file-contents scratch-file)
(set-auto-mode)
t)))
(when (file-readable-p smart-scratch-file)
(message "Reading %s" smart-scratch-file)
(cl-destructuring-bind (content point mode)
(with-temp-buffer
(save-excursion (insert-file-contents smart-scratch-file))
(read (current-buffer)))
(erase-buffer)
(funcall mode)
(insert content)
(goto-char point)
t))))
;;;###autoload
(defun doom-scratch-buffer (&optional mode directory project-name)
(defun doom-scratch-buffer (&optional dont-restore-p mode directory project-name)
"Return a scratchpad buffer in major MODE."
(with-current-buffer
(get-buffer-create (if project-name
(format "*doom:scratch (%s)*" project-name)
"*doom:scratch*"))
(setq default-directory directory)
(unless doom-scratch-current-project
(doom--load-persistent-scratch-buffer project-name)
(when (and (eq major-mode 'fundamental-mode)
(functionp mode))
(funcall mode)))
(cl-pushnew (current-buffer) doom-scratch-buffers)
(add-hook 'kill-buffer-hook #'doom-persist-scratch-buffer-h nil 'local)
(add-hook 'doom-switch-buffer-hook #'doom-persist-scratch-buffers-after-switch-h)
(run-hooks 'doom-scratch-buffer-created-hook)
(current-buffer)))
(let* ((buffer-name (if project-name
(format "*doom:scratch (%s)*" project-name)
"*doom:scratch*"))
(buffer (get-buffer buffer-name)))
(with-current-buffer
(or buffer (get-buffer-create buffer-name))
(setq default-directory directory)
(setq-local so-long--inhibited t)
(if dont-restore-p
(erase-buffer)
(unless buffer
(doom--load-persistent-scratch-buffer project-name)
(when (and (eq major-mode 'fundamental-mode)
(functionp mode))
(funcall mode))))
(cl-pushnew (current-buffer) doom-scratch-buffers)
(add-transient-hook! 'doom-switch-buffer-hook (doom-persist-scratch-buffers-h))
(add-transient-hook! 'doom-switch-window-hook (doom-persist-scratch-buffers-h))
(add-hook 'kill-buffer-hook #'doom-persist-scratch-buffer-h nil 'local)
(run-hooks 'doom-scratch-buffer-created-hook)
(current-buffer))))
;;
@@ -66,11 +81,18 @@ following:
;;;###autoload
(defun doom-persist-scratch-buffer-h ()
"Save the current buffer to `doom-scratch-dir'."
(write-region
(point-min) (point-max)
(expand-file-name (or doom-scratch-current-project
doom-scratch-default-file)
doom-scratch-dir)))
(let ((content (buffer-substring-no-properties (point-min) (point-max)))
(point (point))
(mode major-mode))
(with-temp-file
(expand-file-name (concat (or doom-scratch-current-project
doom-scratch-default-file)
".el")
doom-scratch-dir)
(prin1 (list content
point
mode)
(current-buffer)))))
;;;###autoload
(defun doom-persist-scratch-buffers-h ()
@@ -89,62 +111,66 @@ following:
(remove-hook 'doom-switch-buffer-hook #'doom-persist-scratch-buffers-after-switch-h)))
;;;###autoload
(unless noninteractive
(when doom-interactive-p
(add-hook 'kill-emacs-hook #'doom-persist-scratch-buffers-h))
;;
;;; Commands
(defvar projectile-enable-caching)
;;;###autoload
(defun doom/open-scratch-buffer (&optional arg project-p)
"Opens the (persistent) scratch buffer in a popup.
(defun doom/open-scratch-buffer (&optional arg project-p same-window-p)
"Pop up a persistent scratch buffer.
If passed the prefix ARG, switch to it in the current window.
If passed the prefix ARG, do not restore the last scratch buffer.
If PROJECT-P is non-nil, open a persistent scratch buffer associated with the
current project."
(interactive "P")
(let (projectile-enable-caching)
(funcall
(if arg
(if same-window-p
#'switch-to-buffer
#'pop-to-buffer)
(doom-scratch-buffer
(cond ((eq doom-scratch-buffer-major-mode t)
arg
(cond ((eq doom-scratch-initial-major-mode t)
(unless (or buffer-read-only
(derived-mode-p 'special-mode)
(string-match-p "^ ?\\*" (buffer-name)))
major-mode))
((null doom-scratch-buffer-major-mode)
((null doom-scratch-initial-major-mode)
nil)
((symbolp doom-scratch-buffer-major-mode)
doom-scratch-buffer-major-mode))
((symbolp doom-scratch-initial-major-mode)
doom-scratch-initial-major-mode))
default-directory
(when project-p
(doom-project-name))))))
;;;###autoload
(defun doom/switch-to-scratch-buffer (&optional project-p)
(defun doom/switch-to-scratch-buffer (&optional arg project-p)
"Like `doom/open-scratch-buffer', but switches to it in the current window.
If passed the prefix arg, open project scratch buffer."
If passed the prefix ARG, do not restore the last scratch buffer."
(interactive "P")
(doom/open-scratch-buffer t project-p))
(doom/open-scratch-buffer arg project-p 'same-window))
;;;###autoload
(defun doom/open-project-scratch-buffer (&optional current-window)
(defun doom/open-project-scratch-buffer (&optional arg same-window-p)
"Opens the (persistent) project scratch buffer in a popup.
If passed the prefix arg, switch to it in the current window."
If passed the prefix ARG, do not restore the last scratch buffer."
(interactive "P")
(doom/open-scratch-buffer current-window 'project))
(doom/open-scratch-buffer arg 'project same-window-p))
;;;###autoload
(defun doom/switch-to-project-scratch-buffer ()
(defun doom/switch-to-project-scratch-buffer (&optional arg)
"Like `doom/open-project-scratch-buffer', but switches to it in the current
window."
(interactive)
(doom/open-project-scratch-buffer t))
window.
If passed the prefix ARG, do not restore the last scratch buffer."
(interactive "P")
(doom/open-project-scratch-buffer arg 'same-window))
;;;###autoload
(defun doom/revert-scratch-buffer ()

View File

@@ -47,10 +47,16 @@
"TODO"
(setq file (expand-file-name (or file (doom-session-file))))
(message "Attempting to load %s" file)
(cond ((require 'persp-mode nil t)
(cond ((not (file-readable-p file))
(message "No session file at %S to read from" file))
((require 'persp-mode nil t)
(unless persp-mode
(persp-mode +1))
(persp-load-state-from-file file))
(let ((allowed (persp-list-persp-names-in-file file)))
(cl-loop for name being the hash-keys of *persp-hash*
unless (member name allowed)
do (persp-kill name))
(persp-load-state-from-file file)))
((and (require 'frameset nil t)
(require 'restart-emacs nil t))
(restart-emacs--restore-frames-using-desktop file))
@@ -65,6 +71,9 @@
"TODO"
(add-hook 'window-setup-hook #'doom-load-session 'append))
;;;###autoload
(add-to-list 'command-switch-alist (cons "--restore" #'doom-restore-session-handler))
;;
;;; Commands
@@ -124,5 +133,11 @@
(interactive "P")
(setq doom-autosave-session nil)
(doom/quicksave-session)
(restart-emacs
(delq nil (list (if debug "--debug-init") "--restore"))))
(save-some-buffers nil t)
(letf! ((#'save-buffers-kill-emacs #'kill-emacs)
(confirm-kill-emacs))
(restart-emacs
(append (if debug (list "--debug-init"))
(when (boundp 'chemacs-current-emacs-profile)
(list "--with-profile" chemacs-current-emacs-profile))
(list "--restore")))))

View File

@@ -1,5 +1,17 @@
;;; core/autoload/text.el -*- lexical-binding: t; -*-
(defvar doom-point-in-comment-functions ()
"List of functions to run to determine if point is in a comment.
Each function takes one argument: the position of the point. Stops on the first
function to return non-nil. Used by `doom-point-in-comment-p'.")
(defvar doom-point-in-string-functions ()
"List of functions to run to determine if point is in a string.
Each function takes one argument: the position of the point. Stops on the first
function to return non-nil. Used by `doom-point-in-string-p'.")
;;;###autoload
(defun doom-surrounded-p (pair &optional inline balanced)
"Returns t if point is surrounded by a brace delimiter: {[(
@@ -28,31 +40,18 @@ lines, above and below, with only whitespace in between."
;;;###autoload
(defun doom-point-in-comment-p (&optional pos)
"Return non-nil if POS is in a comment.
POS defaults to the current position."
;; REVIEW Should we cache `syntax-ppss'?
(let* ((pos (or pos (point)))
(ppss (syntax-ppss pos)))
(or (nth 4 ppss)
(nth 8 ppss)
(and (< pos (point-max))
(memq (char-syntax (char-after pos)) '(?< ?>))
(not (eq (char-after pos) ?\n)))
(when-let (s (car (syntax-after pos)))
(or (and (/= 0 (logand (lsh 1 16) s))
(nth 4 (doom-syntax-ppss (+ pos 2))))
(and (/= 0 (logand (lsh 1 17) s))
(nth 4 (doom-syntax-ppss (+ pos 1))))
(and (/= 0 (logand (lsh 1 18) s))
(nth 4 (doom-syntax-ppss (- pos 1))))
(and (/= 0 (logand (lsh 1 19) s))
(nth 4 (doom-syntax-ppss (- pos 2)))))))))
(let ((pos (or pos (point))))
(or (run-hook-with-args-until-success 'doom-point-in-comment-functions pos)
(sp-point-in-comment pos))))
;;;###autoload
(defun doom-point-in-string-p (&optional pos)
"Return non-nil if POS is in a string."
;; REVIEW Should we cache `syntax-ppss'?
(nth 3 (syntax-ppss pos)))
(let ((pos (or pos (point))))
(or (run-hook-with-args-until-success 'doom-point-in-string-functions pos)
(sp-point-in-string pos))))
;;;###autoload
(defun doom-point-in-string-or-comment-p (&optional pos)
@@ -60,74 +59,157 @@ POS defaults to the current position."
(or (doom-point-in-string-p pos)
(doom-point-in-comment-p pos)))
;;;###autoload
(defun doom-region-active-p ()
"Return non-nil if selection is active.
Detects evil visual mode as well."
(declare (side-effect-free t))
(or (use-region-p)
(and (bound-and-true-p evil-local-mode)
(evil-visual-state-p))))
;;;###autoload
(defun doom-region-beginning ()
"Return beginning position of selection.
Uses `evil-visual-beginning' if available."
(declare (side-effect-free t))
(if (bound-and-true-p evil-local-mode)
evil-visual-beginning
(region-beginning)))
;;;###autoload
(defun doom-region-end ()
"Return end position of selection.
Uses `evil-visual-end' if available."
(declare (side-effect-free t))
(if (bound-and-true-p evil-local-mode)
evil-visual-end
(region-end)))
;;;###autoload
(defun doom-thing-at-point-or-region (&optional thing prompt)
"Grab the current selection, THING at point, or xref identifier at point.
Returns THING if it is a string. Otherwise, if nothing is found at point and
PROMPT is non-nil, prompt for a string (if PROMPT is a string it'll be used as
the prompting string). Returns nil if all else fails.
NOTE: Don't use THING for grabbing symbol-at-point. The xref fallback is smarter
in some cases."
(declare (side-effect-free t))
(cond ((stringp thing)
thing)
((doom-region-active-p)
(buffer-substring-no-properties
(doom-region-beginning)
(doom-region-end)))
(thing
(thing-at-point thing t))
((require 'xref nil t)
;; A little smarter than using `symbol-at-point', though in most cases,
;; xref ends up using `symbol-at-point' anyway.
(xref-backend-identifier-at-point (xref-find-backend)))
(prompt
(read-string (if (stringp prompt) prompt "")))))
;;
;;; Commands
(defvar doom--last-backward-pt most-positive-fixnum)
(defun doom--bol-bot-eot-eol (&optional pos)
(save-mark-and-excursion
(when pos
(goto-char pos))
(let* ((bol (if visual-line-mode
(save-excursion
(beginning-of-visual-line)
(point))
(line-beginning-position)))
(bot (save-excursion
(goto-char bol)
(skip-chars-forward " \t\r")
(point)))
(eol (if visual-line-mode
(save-excursion (end-of-visual-line) (point))
(line-end-position)))
(eot (or (save-excursion
(if (not comment-use-syntax)
(progn
(goto-char bol)
(when (re-search-forward comment-start-skip eol t)
(or (match-end 1) (match-beginning 0))))
(goto-char eol)
(while (and (doom-point-in-comment-p)
(> (point) bol))
(backward-char))
(skip-chars-backward " " bol)
(unless (or (eq (char-after) 32) (eolp))
(forward-char))
(point)))
eol)))
(list bol bot eot eol))))
(defvar doom--last-backward-pt nil)
;;;###autoload
(defun doom/backward-to-bol-or-indent ()
(defun doom/backward-to-bol-or-indent (&optional point)
"Jump between the indentation column (first non-whitespace character) and the
beginning of the line. The opposite of
`doom/forward-to-last-non-comment-or-eol'."
(interactive)
(let ((pt (point)))
(cl-destructuring-bind (bol . bot)
(save-excursion
(beginning-of-visual-line)
(cons (point)
(progn (skip-chars-forward " \t\r")
(point))))
(interactive "^d")
(let ((pt (or point (point))))
(cl-destructuring-bind (bol bot _eot _eol)
(doom--bol-bot-eot-eol pt)
(cond ((> pt bot)
(goto-char bot))
((= pt bol)
(goto-char (min doom--last-backward-pt bot))
(setq doom--last-backward-pt most-positive-fixnum))
(or (and doom--last-backward-pt
(= (line-number-at-pos doom--last-backward-pt)
(line-number-at-pos pt)))
(setq doom--last-backward-pt nil))
(goto-char (or doom--last-backward-pt bot))
(setq doom--last-backward-pt nil))
((<= pt bot)
(setq doom--last-backward-pt pt)
(goto-char bol))))))
(defvar doom--last-forward-pt -1)
(defvar doom--last-forward-pt nil)
;;;###autoload
(defun doom/forward-to-last-non-comment-or-eol ()
(defun doom/forward-to-last-non-comment-or-eol (&optional point)
"Jumps between the last non-blank, non-comment character in the line and the
true end of the line. The opposite of `doom/backward-to-bol-or-indent'."
(interactive "^d")
(let ((pt (or point (point))))
(cl-destructuring-bind (_bol _bot eot eol)
(doom--bol-bot-eot-eol pt)
(cond ((< pt eot)
(goto-char eot))
((= pt eol)
(goto-char (or doom--last-forward-pt eot))
(setq doom--last-forward-pt nil))
((>= pt eot)
(setq doom--last-backward-pt pt)
(goto-char eol))))))
;;;###autoload
(defun doom/backward-kill-to-bol-and-indent ()
"Kill line to the first non-blank character. If invoked again afterwards, kill
line to beginning of line. Same as `evil-delete-back-to-indentation'."
(interactive)
(let ((eol (if (not visual-line-mode)
(line-end-position)
(save-excursion (end-of-visual-line) (point)))))
(if (or (and (< (point) eol)
(sp-point-in-comment))
(not (sp-point-in-comment eol)))
(if (= (point) eol)
(progn
(goto-char doom--last-forward-pt)
(setq doom--last-forward-pt -1))
(setq doom--last-forward-pt (point))
(goto-char eol))
(let* ((bol (save-excursion (beginning-of-visual-line) (point)))
(boc (or (save-excursion
(if (not comment-use-syntax)
(progn
(goto-char bol)
(when (re-search-forward comment-start-skip eol t)
(or (match-end 1) (match-beginning 0))))
(goto-char eol)
(while (and (sp-point-in-comment)
(> (point) bol))
(backward-char))
(skip-chars-backward " " bol)
(point)))
eol)))
(when (> doom--last-forward-pt boc)
(setq boc doom--last-forward-pt))
(if (or (= eol (point))
(> boc (point)))
(progn
(goto-char boc)
(setq doom--last-forward-pt -1))
(setq doom--last-forward-pt (point))
(goto-char eol))))))
(let ((empty-line-p (save-excursion (beginning-of-line)
(looking-at-p "[ \t]*$"))))
(funcall (if (fboundp 'evil-delete)
#'evil-delete
#'delete-region)
(point-at-bol) (point))
(unless empty-line-p
(indent-according-to-mode))))
;;;###autoload
(defun doom/delete-backward-word (arg)
"Like `backward-kill-word', but doesn't affect the kill-ring."
(interactive "p")
(let (kill-ring)
(backward-kill-word arg)))
;;;###autoload
(defun doom/dumb-indent ()
@@ -155,20 +237,6 @@ true end of the line. The opposite of `doom/backward-to-bol-or-indent'."
tab-width
(- tab-width movement)))))))))
;;;###autoload
(defun doom/backward-kill-to-bol-and-indent ()
"Kill line to the first non-blank character. If invoked again
afterwards, kill line to beginning of line."
(interactive)
(let ((empty-line-p (save-excursion (beginning-of-line)
(looking-at-p "[ \t]*$"))))
(funcall (if (fboundp 'evil-delete)
#'evil-delete
#'delete-region)
(point-at-bol) (point))
(unless empty-line-p
(indent-according-to-mode))))
;;;###autoload
(defun doom/retab (arg &optional beg end)
"Converts tabs-to-spaces or spaces-to-tabs within BEG and END (defaults to
@@ -192,15 +260,9 @@ opposite indentation style."
Respects `require-final-newline'."
(interactive)
(goto-char (point-max))
(skip-chars-backward " \t\n\v")
(when (looking-at "\n\\(\n\\|\\'\\)")
(forward-char 1))
(when require-final-newline
(unless (bolp)
(insert "\n")))
(when (looking-at "\n+")
(replace-match "")))
(save-excursion
(goto-char (point-max))
(delete-blank-lines)))
;;;###autoload
(defun doom/dos2unix ()

View File

@@ -9,6 +9,13 @@
`((,(car spec) ((t ,(cdr spec))))))
(`((,(car spec) ,(cdr spec))))))
;;;###autoload
(defconst doom-customize-theme-hook nil)
(add-hook! 'doom-load-theme-hook :append
(defun doom-apply-customized-faces-h ()
(run-hooks 'doom-customize-theme-hook)))
;;;###autoload
(defmacro custom-theme-set-faces! (theme &rest specs)
"Apply a list of face SPECS as user customizations for THEME.
@@ -16,26 +23,27 @@
THEME can be a single symbol or list thereof. If nil, apply these settings to
all themes. It will apply to all themes once they are loaded."
(declare (indent defun))
`(let ((fn (gensym "doom--customize-themes-h-")))
(fset
fn (lambda ()
(let (custom--inhibit-theme-enable)
(dolist (theme (doom-enlist (or ,theme 'user)))
(when (or (eq theme 'user)
(custom-theme-enabled-p theme))
(apply #'custom-theme-set-faces theme
(mapcan #'doom--custom-theme-set-face
(list ,@specs))))))))
(when (or doom-init-theme-p (null doom-theme))
(funcall fn))
(add-hook 'doom-load-theme-hook fn 'append)))
(let ((fn (gensym "doom--customize-themes-h-")))
`(progn
(defun ,fn ()
(let (custom--inhibit-theme-enable)
(dolist (theme (doom-enlist (or ,theme 'user)))
(when (or (eq theme 'user)
(custom-theme-enabled-p theme))
(apply #'custom-theme-set-faces theme
(mapcan #'doom--custom-theme-set-face
(list ,@specs)))))))
(when (or doom-init-theme-p (null doom-theme))
(funcall #',fn))
(add-hook 'doom-customize-theme-hook #',fn 'append))))
;;;###autoload
(defmacro custom-set-faces! (&rest specs)
"Apply a list of face SPECS as user customizations.
This is a drop-in replacement for `custom-set-face' that allows for a simplified
face format."
This is a convenience macro alternative to `custom-set-face' which allows for a
simplified face format, and takes care of load order issues, so you can use
doom-themes' API without worry."
(declare (indent defun))
`(custom-theme-set-faces! 'user ,@specs))

View File

@@ -1,7 +1,7 @@
;;; core/autoload/ui.el -*- lexical-binding: t; -*-
;;
;; Public library
;;; Public library
;;;###autoload
(defun doom-resize-window (window new-size &optional horizontal force-p)
@@ -24,13 +24,23 @@ are open."
;;
;; Advice
;;; Advice
;;;###autoload
(defun doom-recenter-a (&rest _)
"Generic advisor for recentering window (typically :after other functions)."
"Generic advice for recentering window (typically :after other functions)."
(recenter))
;;;###autoload
(defun doom-preserve-window-position-a (orig-fn &rest args)
"Generic advice for preserving cursor position on screen after scrolling."
(let ((row (cdr (posn-col-row (posn-at-point)))))
(prog1 (apply orig-fn args)
(save-excursion
(let ((target-row (- (line-number-at-pos) row)))
(unless (< target-row 0)
(evil-scroll-line-to-top target-row)))))))
;;;###autoload
(defun doom-shut-up-a (orig-fn &rest args)
"Generic advisor for silencing noisy functions.
@@ -43,7 +53,7 @@ In tty Emacs, messages suppressed completely."
;;
;; Hooks
;;; Hooks
;;;###autoload
(defun doom-apply-ansi-color-to-compilation-buffer-h ()
@@ -57,9 +67,17 @@ In tty Emacs, messages suppressed completely."
"Turn off `show-paren-mode' buffer-locally."
(setq-local show-paren-mode nil))
;;;###autoload
(defun doom-enable-line-numbers-h ()
(display-line-numbers-mode +1))
;;;###autoload
(defun doom-disable-line-numbers-h ()
(display-line-numbers-mode -1))
;;
;; Commands
;;; Commands
;;;###autoload
(defun doom/toggle-line-numbers ()
@@ -87,7 +105,7 @@ See `display-line-numbers' for what these values mean."
(_ (symbol-name next))))))
;;;###autoload
(defun doom/delete-frame ()
(defun doom/delete-frame-with-prompt ()
"Delete the current frame, but ask for confirmation if it isn't empty."
(interactive)
(if (cdr (frame-list))
@@ -95,6 +113,7 @@ See `display-line-numbers' for what these values mean."
(delete-frame))
(save-buffers-kill-emacs)))
(defvar doom--maximize-last-wconf nil)
;;;###autoload
(defun doom/window-maximize-buffer ()
"Close other windows to focus on this one. Activate again to undo this. If the
@@ -102,41 +121,40 @@ window changes before then, the undo expires.
Alternatively, use `doom/window-enlargen'."
(interactive)
(if (and (one-window-p)
(assq ?_ register-alist))
(jump-to-register ?_)
(when (and (bound-and-true-p +popup-mode)
(+popup-window-p))
(user-error "Cannot maximize a popup, use `+popup/raise' first or use `doom/window-enlargen' instead"))
(window-configuration-to-register ?_)
(delete-other-windows)))
(setq doom--maximize-last-wconf
(if (and (null (cdr (cl-remove-if #'window-dedicated-p (window-list))))
doom--maximize-last-wconf)
(ignore (set-window-configuration doom--maximize-last-wconf))
(when (and (bound-and-true-p +popup-mode)
(+popup-window-p))
(user-error "Cannot maximize a popup, use `+popup/raise' first or use `doom/window-enlargen' instead"))
(prog1 (current-window-configuration)
(delete-other-windows)))))
(defvar doom--window-enlargened nil)
(defvar doom--enlargen-last-wconf nil)
;;;###autoload
(defun doom/window-enlargen ()
"Enlargen the current window to focus on this one. Does not close other
windows (unlike `doom/window-maximize-buffer') Activate again to undo."
windows (unlike `doom/window-maximize-buffer'). Activate again to undo."
(interactive)
(setq doom--window-enlargened
(if (and doom--window-enlargened
(assq ?_ register-alist))
(ignore (ignore-errors (jump-to-register ?_)))
(window-configuration-to-register ?_)
(let* ((window (selected-window))
(dedicated-p (window-dedicated-p window))
(preserved-p (window-parameter window 'window-preserved-size))
(ignore-window-parameters t))
(unwind-protect
(progn
(when dedicated-p
(set-window-dedicated-p window nil))
(when preserved-p
(set-window-parameter window 'window-preserved-size nil))
(maximize-window window))
(set-window-dedicated-p window dedicated-p)
(when preserved-p
(set-window-parameter window 'window-preserved-size preserved-p)))
t))))
(setq doom--enlargen-last-wconf
(if doom--enlargen-last-wconf
(ignore (set-window-configuration doom--enlargen-last-wconf))
(prog1 (current-window-configuration)
(let* ((window (selected-window))
(dedicated-p (window-dedicated-p window))
(preserved-p (window-parameter window 'window-preserved-size))
(ignore-window-parameters t))
(unwind-protect
(progn
(when dedicated-p
(set-window-dedicated-p window nil))
(when preserved-p
(set-window-parameter window 'window-preserved-size nil))
(maximize-window window))
(set-window-dedicated-p window dedicated-p)
(when preserved-p
(set-window-parameter window 'window-preserved-size preserved-p))))))))
;;;###autoload
(defun doom/window-maximize-horizontally ()

View File

@@ -1,407 +1,225 @@
;;; core/cli/autoloads.el -*- lexical-binding: t; -*-
(defvar doom-autoload-excluded-packages '("gh")
"Packages that have silly or destructive autoload files that try to load
(defvar doom-autoloads-excluded-packages '("gh")
"What packages whose autoloads file we won't index.
These packages have silly or destructive autoload files that try to load
everyone in the universe and their dog, causing errors that make babies cry. No
one wants that.")
;; externs
(defvar doom-autoloads-cached-vars
'(doom-modules
doom-disabled-packages
load-path
auto-mode-alist
interpreter-mode-alist
Info-directory-list)
"A list of variables to be cached in `doom-autoload-file'.")
(defvar doom-autoloads-files ()
"A list of additional files or file globs to scan for autoloads.")
;;
;;; Library
(defun doom-autoloads-reload (&optional file)
"Regenerates Doom's autoloads and writes them to FILE."
(unless file
(setq file doom-autoload-file))
(print! (start "(Re)generating autoloads file..."))
(print-group!
(cl-check-type file string)
(doom-initialize-packages)
(and (print! (start "Generating autoloads file..."))
(doom-autoloads--write
file
`((unless (equal emacs-major-version ,emacs-major-version)
(signal 'doom-error
(list "The installed version of Emacs has changed since last 'doom sync' ran"
"Run 'doom sync && doom build' to bring Doom up to speed")))
(unless (equal doom-version ,doom-version)
(signal 'doom-error
(list "The installed version of Doom has changed since last 'doom sync' ran"
"Run 'doom sync' to bring Doom up to speed"))))
(mapcar (lambda (var) `(set ',var ',(symbol-value var)))
doom-autoloads-cached-vars)
(doom-autoloads--scan
(append (cl-loop for dir
in (append (list doom-core-dir)
(cdr (doom-module-load-path 'all-p))
(list doom-private-dir))
if (doom-glob dir "autoload.el") collect it
if (doom-glob dir "autoload/*.el") append it)
(mapcan #'doom-glob doom-autoloads-files)))
(doom-autoloads--scan
(mapcar #'straight--autoloads-file
(seq-difference (hash-table-keys straight--build-cache)
doom-autoloads-excluded-packages))
'literal))
(print! (start "Byte-compiling autoloads file..."))
(doom-autoloads--compile-file file)
(print! (success "Generated %s")
(relpath (byte-compile-dest-file file)
doom-emacs-dir)))))
(defun doom-autoloads--write (file &rest forms)
(make-directory (file-name-directory file) 'parents)
(condition-case-unless-debug e
(with-temp-file file
(setq-local coding-system-for-write 'utf-8)
(let ((standard-output (current-buffer))
(print-quoted t)
(print-level nil)
(print-length nil))
(insert ";; -*- lexical-binding: t; coding: utf-8; -*-\n"
";; This file is autogenerated by 'doom sync', DO NOT EDIT IT!!\n")
(dolist (form (delq nil forms))
(mapc #'prin1 form))
t))
(error (delete-file file)
(signal 'doom-autoload-error (list file e)))))
(defun doom-autoloads--compile-file (file)
(condition-case-unless-debug e
(let ((byte-compile-warnings (if doom-debug-p byte-compile-warnings)))
(and (byte-compile-file file)
(load (byte-compile-dest-file file) nil t)))
(error
(delete-file (byte-compile-dest-file file))
(signal 'doom-autoload-error (list file e)))))
(defun doom-autoloads--cleanup-form (form &optional expand)
(let ((func (car-safe form)))
(cond ((memq func '(provide custom-autoload))
nil)
((and (eq func 'add-to-list)
(memq (doom-unquote (cadr form))
doom-autoloads-cached-vars))
nil)
((not (eq func 'autoload))
form)
((and expand (not (file-name-absolute-p (nth 2 form))))
(defvar doom--autoloads-path-cache nil)
(setf (nth 2 form)
(let ((path (nth 2 form)))
(or (cdr (assoc path doom--autoloads-path-cache))
(when-let* ((libpath (locate-library path))
(libpath (file-name-sans-extension libpath))
(libpath (abbreviate-file-name libpath)))
(push (cons path libpath) doom--autoloads-path-cache)
libpath)
path)))
form)
(form))))
(defun doom-autoloads--scan-autodefs (file buffer module &optional module-enabled-p)
(with-temp-buffer
(insert-file-contents file)
(while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t)
(let* ((standard-output buffer)
(form (read (current-buffer)))
(altform (match-string 1))
(definer (car-safe form))
(symbol (doom-unquote (cadr form))))
(cond ((and (not module-enabled-p) altform)
(print (read altform)))
((memq definer '(defun defmacro cl-defun cl-defmacro))
(if module-enabled-p
(print (make-autoload form file))
(cl-destructuring-bind (_ _ arglist &rest body) form
(print
(if altform
(read altform)
(append
(list (pcase definer
(`defun 'defmacro)
(`cl-defun `cl-defmacro)
(_ type))
symbol arglist
(format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
module
(if (stringp (car body))
(pop body)
"No documentation.")))
(cl-loop for arg in arglist
if (and (symbolp arg)
(not (keywordp arg))
(not (memq arg cl--lambda-list-keywords)))
collect arg into syms
else if (listp arg)
collect (car arg) into syms
finally return (if syms `((ignore ,@syms)))))))))
(print `(put ',symbol 'doom-module ',module)))
((eq definer 'defalias)
(cl-destructuring-bind (_ _ target &optional docstring) form
(unless module-enabled-p
(setq target #'ignore
docstring
(format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
module docstring)))
(print `(put ',symbol 'doom-module ',module))
(print `(defalias ',symbol #',(doom-unquote target) ,docstring))))
(module-enabled-p (print form)))))))
(defvar autoload-timestamps)
(defvar generated-autoload-load-name)
(defvar generated-autoload-file)
;;
;;; Helpers
(defun doom--cli-delete-autoloads-file (file)
"Delete FILE (an autoloads file) and accompanying *.elc file, if any."
(cl-check-type file string)
(when (file-exists-p file)
(when-let (buf (find-buffer-visiting file))
(with-current-buffer buf
(set-buffer-modified-p nil))
(kill-buffer buf))
(delete-file file)
(ignore-errors (delete-file (byte-compile-dest-file file)))
t))
(defun doom--cli-warn-refresh-session-h ()
(message "Restart or reload Doom Emacs for changes to take effect:\n")
(message " M-x doom/restart-and-restore")
(message " M-x doom/restart")
(message " M-x doom/reload"))
(defun doom--cli-byte-compile-file (file)
(let ((byte-compile-warnings (if doom-debug-mode byte-compile-warnings))
(byte-compile-dynamic t)
(byte-compile-dynamic-docstrings t))
(condition-case-unless-debug e
(when (byte-compile-file file)
(unless doom-interactive-mode
(add-hook 'doom-cli-post-success-execute-hook #'doom--cli-warn-refresh-session-h))
(let (noninteractive)
(load file 'noerror 'nomessage 'nosuffix)))
((debug error)
(let ((backup-file (concat file ".bk")))
(print! (warn "Copied backup to %s") (relpath backup-file))
(copy-file file backup-file 'overwrite))
(doom--cli-delete-autoloads-file file)
(signal 'doom-autoload-error (list file e))))))
(defun doom-cli-reload-autoloads (&optional file force-p)
"Reloads FILE (an autoload file), if it needs reloading.
FILE should be one of `doom-autoload-file' or `doom-package-autoload-file'. If
it is nil, it will try to reload both. If FORCE-P (universal argument) do it
even if it doesn't need reloading!"
(or (null file)
(stringp file)
(signal 'wrong-type-argument (list 'stringp file)))
(if (stringp file)
(cond ((file-equal-p file doom-autoload-file)
(doom-cli-reload-core-autoloads force-p))
((file-equal-p file doom-package-autoload-file)
(doom-cli-reload-package-autoloads force-p))
((error "Invalid autoloads file: %s" file)))
(doom-cli-reload-core-autoloads force-p)
(doom-cli-reload-package-autoloads force-p)))
;;
;;; Doom autoloads
(defun doom--cli-generate-header (func)
(goto-char (point-min))
(insert ";; -*- lexical-binding:t; -*-\n"
";; This file is autogenerated by `" (symbol-name func) "', DO NOT EDIT !!\n\n"))
(defun doom--cli-generate-autoloads (targets)
(let ((n 0))
(dolist (file targets)
(insert
(with-temp-buffer
(cond ((not (doom-file-cookie-p file "if" t))
(print! (debug "Ignoring %s") (relpath file)))
((let ((generated-autoload-load-name (file-name-sans-extension file))
;; Prevent `autoload-find-file' from firing file hooks,
;; e.g. adding to recentf.
find-file-hook
write-file-functions
;; Prevent a possible source of crashes when there's a
;; syntax error in the autoloads file
debug-on-error)
(quiet! (autoload-generate-file-autoloads file (current-buffer))))
(print! (debug "Nothing in %s") (relpath file)))
((cl-incf n)
(print! (debug "Scanning %s...") (relpath file))))
(buffer-string))))
(print! (class (if (> n 0) 'success 'info)
"Scanned %d file(s)")
n)))
(defun doom--cli-expand-autoload-paths (&optional allow-internal-paths)
(let ((load-path
;; NOTE With `doom-private-dir' in `load-path', Doom autoloads files
;; will be unable to declare autoloads for the built-in autoload.el
;; Emacs package, should $DOOMDIR/autoload.el exist. Not sure why
;; they'd want to though, so it's an acceptable compromise.
(append (list doom-private-dir)
doom-modules-dirs
(straight--directory-files (straight--build-dir) nil t)
load-path)))
(defvar doom--autoloads-path-cache nil)
(while (re-search-forward "^\\s-*(\\(?:custom-\\)?autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t)
(let ((path (match-string 1)))
(replace-match
(or (cdr (assoc path doom--autoloads-path-cache))
(when-let* ((libpath (or (and allow-internal-paths
(locate-library path nil (cons doom-emacs-dir doom-modules-dirs)))
(locate-library path)))
(libpath (file-name-sans-extension libpath))
(libpath (abbreviate-file-name libpath)))
(push (cons path libpath) doom--autoloads-path-cache)
libpath)
path)
t t nil 1)))))
(defun doom--cli-generate-autodefs-1 (path &optional member-p)
(let (forms)
(while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t)
(let* ((sexp (sexp-at-point))
(alt-sexp (match-string 1))
(type (car sexp))
(name (doom-unquote (cadr sexp)))
(origin (doom-module-from-path path)))
(cond
((and (not member-p)
alt-sexp)
(push (read alt-sexp) forms))
((memq type '(defun defmacro cl-defun cl-defmacro))
(cl-destructuring-bind (_ _name arglist &rest body) sexp
(appendq!
forms
(list (if member-p
(make-autoload sexp path)
(let ((docstring
(format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
origin
(if (stringp (car body))
(pop body)
"No documentation."))))
(condition-case-unless-debug e
(if alt-sexp
(read alt-sexp)
(append
(list (pcase type
(`defun 'defmacro)
(`cl-defun `cl-defmacro)
(_ type))
name arglist docstring)
(cl-loop for arg in arglist
if (and (symbolp arg)
(not (keywordp arg))
(not (memq arg cl--lambda-list-keywords)))
collect arg into syms
else if (listp arg)
collect (car arg) into syms
finally return (if syms `((ignore ,@syms))))))
('error
(print! "- Ignoring autodef %s (%s)" name e)
nil))))
`(put ',name 'doom-module ',origin)))))
((eq type 'defalias)
(cl-destructuring-bind (_type name target &optional docstring) sexp
(let ((name (doom-unquote name))
(target (doom-unquote target)))
(unless member-p
(setq target #'ignore
docstring
(format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
origin docstring)))
(appendq! forms `((put ',name 'doom-module ',origin)
(defalias ',name #',target ,docstring))))))
(member-p (push sexp forms)))))
forms))
(defun doom--cli-generate-autodefs (targets enabled-targets)
(goto-char (point-max))
(search-backward ";;;***" nil t)
(save-excursion (insert "\n"))
(dolist (path targets)
(insert
(with-temp-buffer
(insert-file-contents path)
(if-let (forms (doom--cli-generate-autodefs-1 path (member path enabled-targets)))
(concat (mapconcat #'prin1-to-string (nreverse forms) "\n")
"\n")
"")))))
(defun doom--cli-cleanup-autoloads ()
(goto-char (point-min))
(when (re-search-forward "^;;\\(;[^\n]*\\| no-byte-compile: t\\)\n" nil t)
(replace-match "" t t)))
(defun doom-cli-reload-core-autoloads (&optional force-p)
"Refreshes `doom-autoload-file', if necessary (or if FORCE-P is non-nil).
It scans and reads autoload cookies (;;;###autoload) in core/autoload/*.el,
modules/*/*/autoload.el and modules/*/*/autoload/*.el, and generates
`doom-autoload-file'.
Run this whenever your `doom!' block, or a module autoload file, is modified."
(require 'autoload)
(let* ((default-directory doom-emacs-dir)
(doom-modules (doom-modules))
(defun doom-autoloads--scan-file (file)
(let* (;; Prevent `autoload-find-file' from firing file hooks, e.g. adding
;; to recentf.
find-file-hook
write-file-functions
;; Prevent a possible source of crashes when there's a syntax error
;; in the autoloads file
debug-on-error
;; The following bindings are in `package-generate-autoloads'.
;; Presumably for a good reason, so I just copied them
(backup-inhibited t)
(version-control 'never)
(case-fold-search nil) ; reduce magic
(autoload-timestamps nil)
case-fold-search ; reduce magic
autoload-timestamps ; reduce noise in generated files
;; Needed for `autoload-generate-file-autoloads'
(generated-autoload-load-name (file-name-sans-extension file))
(target-buffer (current-buffer))
(module (doom-module-from-path file))
(module-enabled-p (and (or (memq (car module) '(:core :private))
(doom-module-p (car module) (cdr module)))
(doom-file-cookie-p file "if" t))))
(save-excursion
(when module-enabled-p
(quiet! (autoload-generate-file-autoloads file target-buffer)))
(doom-autoloads--scan-autodefs
file target-buffer module module-enabled-p))))
;; Where we'll store the files we'll scan for autoloads. This should
;; contain *all* autoload files, even in disabled modules, so we can
;; scan those for autodefs. We start with the core libraries.
(targets (doom-glob doom-core-dir "autoload/*.el"))
;; A subset of `targets' in enabled modules
(active-targets (copy-sequence targets)))
(dolist (path (doom-module-load-path 'all-p))
(when-let* ((files (cons (doom-glob path "autoload.el")
(doom-files-in (doom-path path "autoload")
:match "\\.el$")))
(files (delq nil files)))
(appendq! targets files)
(when (or (doom-module-from-path path 'enabled-only)
(file-equal-p path doom-private-dir))
(appendq! active-targets files))))
(print! (start "Checking core autoloads file"))
(print-group!
(if (and (not force-p)
(file-exists-p doom-autoload-file)
(not (file-newer-than-file-p doom-emacs-dir doom-autoload-file))
(not (cl-loop for dir
in (append (doom-glob doom-private-dir "init.el*")
targets)
if (file-newer-than-file-p dir doom-autoload-file)
return t)))
(ignore
(print! (success "Skipping core autoloads, they are up-to-date"))
(doom-load-autoloads-file doom-autoload-file))
(if (doom--cli-delete-autoloads-file doom-autoload-file)
(print! (success "Deleted old %s") (filename doom-autoload-file))
(make-directory (file-name-directory doom-autoload-file) t))
(print! (start "Regenerating core autoloads file"))
(print-group!
(with-temp-file doom-autoload-file
(doom--cli-generate-header 'doom-cli-reload-core-autoloads)
(save-excursion
(doom--cli-generate-autoloads active-targets)
(print! (success "Generated new autoloads.el")))
;; Replace autoload paths (only for module autoloads) with absolute
;; paths for faster resolution during load and simpler `load-path'
(save-excursion
(doom--cli-expand-autoload-paths 'allow-internal-paths)
(print! (success "Expanded module autoload paths")))
;; Generates stub definitions for functions/macros defined in disabled
;; modules, so that you will never get a void-function when you use
;; them.
(save-excursion
(doom--cli-generate-autodefs targets (reverse active-targets))
(print! (success "Generated autodefs")))
;; Remove byte-compile-inhibiting file variables so we can byte-compile
;; the file, and autoload comments.
(doom--cli-cleanup-autoloads)
(print! (success "Cleaned up autoloads"))))
;; Byte compile it to give the file a chance to reveal errors (and buy us a
;; few marginal performance boosts)
(print! "> Byte-compiling %s..." (relpath doom-autoload-file))
(when (doom--cli-byte-compile-file doom-autoload-file)
(print-group!
(print! (success "Compiled %s") (relpath doom-autoload-file)))))
t)))
;;
;;; Package autoloads
(defun doom--generate-package-autoloads ()
"Concatenates package autoload files, let-binds `load-file-name' around
them,and remove unnecessary `provide' statements or blank links."
(dolist (pkg (hash-table-keys straight--build-cache))
(unless (member pkg doom-autoload-excluded-packages)
(let ((file (straight--autoloads-file pkg)))
(when (file-exists-p file)
(insert-file-contents file)
(save-excursion
(while (re-search-forward "\\(?:\\_<load-file-name\\|#\\$\\)\\_>" nil t)
;; `load-file-name' is meaningless in a concatenated
;; mega-autoloads file, so we replace references to it and #$ with
;; the file they came from.
(unless (doom-point-in-string-or-comment-p)
(replace-match (prin1-to-string (abbreviate-file-name file))
t t))))
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t)
(unless (doom-point-in-string-p)
(replace-match "" t t)))
(unless (bolp) (insert "\n")))))))
(defun doom--generate-var-cache ()
"Print a `setq' form for expensive-to-initialize variables, so we can cache
them in Doom's autoloads file."
(doom-initialize-packages)
(prin1 `(setq load-path ',load-path
auto-mode-alist ',auto-mode-alist
Info-directory-list ',Info-directory-list
doom-disabled-packages ',doom-disabled-packages)
(current-buffer)))
(defun doom--cleanup-package-autoloads ()
"Remove (some) forms that modify `load-path' or `auto-mode-alist'.
These variables are cached all at once and at later, so these removed statements
served no purpose but to waste cycles."
(while (re-search-forward "^\\s-*\\((\\(?:add-to-list\\|\\(?:when\\|if\\) (boundp\\)\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)" nil t)
(goto-char (match-beginning 1))
(kill-sexp)))
(defun doom-cli-reload-package-autoloads (&optional force-p)
"Compiles `doom-package-autoload-file' from the autoloads files of all
installed packages. It also caches `load-path', `Info-directory-list',
`doom-disabled-packages', `package-activated-list' and `auto-mode-alist'.
Will do nothing if none of your installed packages have been modified. If
FORCE-P (universal argument) is non-nil, regenerate it anyway.
This should be run whenever your `doom!' block or update your packages."
(defun doom-autoloads--scan (files &optional literal)
(require 'autoload)
(print! (start "Checking package autoloads file"))
(print-group!
(if (and (not force-p)
(file-exists-p doom-package-autoload-file)
(not (file-newer-than-file-p package-user-dir doom-package-autoload-file))
(not (cl-loop for dir in (straight--directory-files (straight--build-dir))
if (cl-find-if
(lambda (dir)
(file-newer-than-file-p dir doom-package-autoload-file))
(doom-glob (straight--build-dir dir) "*.el"))
return t))
(not (cl-loop with doom-modules = (doom-modules)
for key being the hash-keys of doom-modules
for path = (doom-module-path (car key) (cdr key) "packages.el")
if (file-newer-than-file-p path doom-package-autoload-file)
return t)))
(ignore
(print! (success "Skipping package autoloads, they are up-to-date"))
(doom-load-autoloads-file doom-package-autoload-file))
(let (;; The following bindings are in `package-generate-autoloads'.
;; Presumably for a good reason, so I just copied them
(backup-inhibited t)
(version-control 'never)
(case-fold-search nil) ; reduce magic
(autoload-timestamps nil))
(if (doom--cli-delete-autoloads-file doom-package-autoload-file)
(print! (success "Deleted old %s") (filename doom-package-autoload-file))
(make-directory (file-name-directory doom-autoload-file) t))
(print! (start "Regenerating package autoloads file"))
(print-group!
(with-temp-file doom-package-autoload-file
(doom--cli-generate-header 'doom-cli-reload-package-autoloads)
(save-excursion
;; Cache important and expensive-to-initialize state here.
(doom--generate-var-cache)
(print! (success "Cached package state"))
;; Concatenate the autoloads of all installed packages.
(doom--generate-package-autoloads)
(print! (success "Package autoloads included")))
;; Replace autoload paths (only for module autoloads) with absolute
;; paths for faster resolution during load and simpler `load-path'
(save-excursion
(doom--cli-expand-autoload-paths)
(print! (success "Expanded module autoload paths")))
;; Remove `load-path' and `auto-mode-alist' modifications (most of them,
;; at least); they are cached later, so all those membership checks are
;; unnecessary overhead.
(doom--cleanup-package-autoloads)
(print! (success "Removed load-path/auto-mode-alist entries"))))
;; Byte compile it to give the file a chance to reveal errors (and buy us a
;; few marginal performance boosts)
(print! (start "Byte-compiling %s...") (relpath doom-package-autoload-file))
(when (doom--cli-byte-compile-file doom-package-autoload-file)
(print-group!
(print! (success "Compiled %s") (relpath doom-package-autoload-file))))))
t))
(let (autoloads)
(dolist (file
(seq-filter #'file-readable-p files)
(nreverse (delq nil autoloads)))
(with-temp-buffer
(print! (debug "- Scanning %s") (relpath file doom-emacs-dir))
(if literal
(insert-file-contents file)
(doom-autoloads--scan-file file))
(save-excursion
(let ((filestr (prin1-to-string file)))
(while (re-search-forward "\\_<load-file-name\\_>" nil t)
;; `load-file-name' is meaningless in a concatenated
;; mega-autoloads file, so we replace references to it with the
;; file they came from.
(let ((ppss (save-excursion (syntax-ppss))))
(or (nth 3 ppss)
(nth 4 ppss)
(replace-match filestr t t))))))
(let ((load-file-name file)
(load-path
(append (list doom-private-dir)
doom-modules-dirs
load-path)))
(condition-case _
(while t
(push (doom-autoloads--cleanup-form (read (current-buffer))
(not literal))
autoloads))
(end-of-file)))))))

View File

@@ -2,7 +2,9 @@
(defcli! (compile c)
((recompile-p ["-r" "--recompile"])
&rest targets)
(core-p ["-c" "--core"])
(private-p ["-p" "--private"])
(verbose-p ["-v" "--verbose"]))
"Byte-compiles your config or selected modules.
compile [TARGETS...]
@@ -11,8 +13,23 @@
Accepts :core and :private as special arguments, which target Doom's core files
and your private config files, respectively. To recompile your packages, use
'doom rebuild' instead."
(doom-cli-byte-compile targets recompile-p))
'doom build' instead."
(doom-cli-byte-compile
(if (or core-p private-p)
(append (when core-p
(list (doom-glob doom-emacs-dir "init.el")
doom-core-dir))
(when private-p
(list doom-private-dir)))
(append (list (doom-glob doom-emacs-dir "init.el")
doom-core-dir)
(cl-remove-if-not
;; Only compile Doom's modules
(lambda (path) (file-in-directory-p path doom-emacs-dir))
;; Omit `doom-private-dir', which is always first
(cdr (doom-module-load-path)))))
recompile-p
verbose-p))
(defcli! clean ()
"Delete all *.elc files."
@@ -25,16 +42,20 @@ and your private config files, respectively. To recompile your packages, use
(defun doom--byte-compile-ignore-file-p (path)
(let ((filename (file-name-nondirectory path)))
(or (string-prefix-p "." filename)
(or (not (equal (file-name-extension path) "el"))
(member filename (list "packages.el" "doctor.el"))
(string-prefix-p "." filename)
(string-prefix-p "test-" filename)
(not (equal (file-name-extension path) "el"))
(member filename (list "packages.el" "doctor.el")))))
(string-prefix-p "flycheck_" filename)
(string-suffix-p ".example.el" filename))))
(cl-defun doom-cli-byte-compile (&optional modules recompile-p)
(cl-defun doom-cli-byte-compile (&optional targets recompile-p verbose-p)
"Byte compiles your emacs configuration.
init.el is always byte-compiled by this.
If TARGETS is specified, as a list of direcotries
If MODULES is specified (a list of module strings, e.g. \"lang/php\"), those are
byte-compiled. Otherwise, all enabled modules are byte-compiled, including Doom
core. It always ignores unit tests and files with `no-byte-compile' enabled.
@@ -47,138 +68,109 @@ Use `doom-clean-byte-compiled-files' or `make clean' to reverse
byte-compilation.
If RECOMPILE-P is non-nil, only recompile out-of-date files."
(let ((default-directory doom-emacs-dir)
(doom-modules (doom-modules))
(byte-compile-verbose doom-debug-mode)
(byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
;; In case it is changed during compile-time
(auto-mode-alist auto-mode-alist)
(noninteractive t)
targets)
(let (target-dirs)
(dolist (module (delete-dups modules))
(pcase module
(":core"
(push (doom-glob doom-emacs-dir "init.el") targets)
(push doom-core-dir target-dirs))
(":private"
(push doom-private-dir target-dirs))
((pred file-directory-p)
(push module target-dirs))
((pred (string-match "^\\([^/]+\\)/\\([^/]+\\)$"))
(push (doom-module-locate-path
(doom-keyword-intern (match-string 1 module))
(intern (match-string 2 module)))
target-dirs))
(_ (user-error "%S is not a valid target" module))))
(and (or (null modules) (member ":private" modules))
(not recompile-p)
(not (or doom-auto-accept
(y-or-n-p
(concat "Warning: byte compiling is for advanced users. It will interfere with your\n"
"efforts to debug issues. It is not recommended you do it if you frequently\n"
"tinker with your Emacs config.\n\n"
"Alternatively, use `bin/doom compile :core` instead to byte-compile only the\n"
"Doom core files, as these don't change often.\n\n"
"If you have issues, please make sure byte-compilation isn't the cause by using\n"
"`bin/doom clean` to clear out your *.elc files.\n\n"
"Byte-compile anyway?"))))
(user-error "Aborting"))
(let* ((default-directory doom-emacs-dir)
(targets (nreverse (delete-dups targets)))
;; In case it is changed during compile-time
(auto-mode-alist auto-mode-alist)
kill-emacs-hook kill-buffer-query-functions)
(let ((after-load-functions
(if (null targets)
after-load-functions
;; Assemble el files we want to compile, and preserve in the order
;; they are loaded in, so we don't run into any scary catch-22s
;; while byte-compiling, like missing macros.
(cons (let ((target-dirs (cl-remove-if-not #'file-directory-p targets)))
(lambda (path)
(and (not (doom--byte-compile-ignore-file-p path))
(cl-find-if (doom-partial #'file-in-directory-p path)
target-dirs)
(cl-pushnew path targets))))
after-load-functions))))
(doom-log "Reloading Doom in preparation for byte-compilation")
;; But first we must be sure that Doom and your private config have been
;; fully loaded. Which usually aren't so in an noninteractive session.
(let ((doom-interactive-mode 'byte-compile))
(doom-initialize)
(doom-initialize-packages)
(doom-initialize-core))
(let ((load-prefer-newer t)
(noninteractive t)
doom-interactive-p)
(doom-initialize 'force)
(quiet! (doom-initialize-packages))))
;;
(unless target-dirs
(push (doom-glob doom-emacs-dir "init.el") targets)
;; If no targets were supplied, then we use your module list.
(appendq! target-dirs
(list doom-core-dir)
(nreverse
(cl-remove-if-not
(lambda (path) (file-in-directory-p path doom-emacs-dir))
;; Omit `doom-private-dir', which is always first
(cdr (doom-module-load-path))))))
(if (null targets)
(print! (info "No targets to %scompile" (if recompile-p "re" "")))
(print! (start "%scompiling your config...")
(if recompile-p "Re" "Byte-"))
;; Assemble el files we want to compile; taking into account that MODULES
;; may be a list of MODULE/SUBMODULE strings from the command line.
(appendq! targets
(doom-files-in target-dirs
:match "\\.el$"
:filter #'doom--byte-compile-ignore-file-p)))
(dolist (dir
(cl-remove-if-not #'file-directory-p targets)
(setq targets (cl-remove-if #'file-directory-p targets)))
(prependq! targets
(doom-files-in
dir :match "\\.el" :filter #'doom--byte-compile-ignore-file-p)))
(unless targets
(print!
(if targets
(warn "Couldn't find any valid targets")
(info "No targets to %scompile" (if recompile-p "re" ""))))
(cl-return nil))
(print!
(start (if recompile-p
"Recompiling stale elc files..."
"Byte-compiling your config (may take a while)...")))
(print-group!
(require 'use-package)
(condition-case e
(let ((total-ok 0)
(total-fail 0)
(total-noop 0)
(use-package-defaults use-package-defaults)
(use-package-expand-minimally t)
kill-emacs-hook kill-buffer-query-functions)
;; Prevent packages from being loaded at compile time if they
;; don't meet their own predicates.
(push (list :no-require t
(lambda (_name args)
(or (when-let (pred (or (plist-get args :if)
(plist-get args :when)))
(not (eval pred t)))
(when-let (pred (plist-get args :unless))
(eval pred t)))))
use-package-defaults)
(unless recompile-p
(doom-clean-byte-compiled-files))
(dolist (target (delete-dups (delq nil targets)))
(cl-incf
(if (not (or (not recompile-p)
(let ((elc-file (byte-compile-dest-file target)))
(and (file-exists-p elc-file)
(file-newer-than-file-p target elc-file)))))
total-noop
(pcase (if (doom-file-cookie-p target "if" t)
(byte-compile-file target)
'no-byte-compile)
(`no-byte-compile
(print! (info "Ignored %s") (relpath target))
total-noop)
(`nil
(print! (error "Failed to compile %s") (relpath target))
total-fail)
(_
(print! (success "Compiled %s") (relpath target))
(load target t t)
total-ok)))))
(print! (class (if (= total-fail 0) 'success 'error)
"%s %d/%d file(s) (%d ignored)")
(if recompile-p "Recompiled" "Compiled")
total-ok (- (length targets) total-noop)
total-noop)
t)
((debug error)
(print! (error "\nThere were breaking errors.\n\n%s")
"Reverting changes...")
(signal 'doom-error (list 'byte-compile e)))))))
(print-group!
(require 'use-package)
(condition-case-unless-debug e
(let* ((total-ok 0)
(total-fail 0)
(total-noop 0)
(byte-compile-verbose nil)
(byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
(byte-compile-dynamic-docstrings t)
(use-package-compute-statistics nil)
(use-package-defaults use-package-defaults)
(use-package-expand-minimally t)
(targets (delete-dups targets))
(modules (seq-group-by #'doom-module-from-path targets))
(total-files (length targets))
(total-modules (length modules))
(i 0)
last-module)
;; Prevent packages from being loaded at compile time if they
;; don't meet their own predicates.
(push (list :no-require t
(lambda (_name args)
(or (when-let (pred (or (plist-get args :if)
(plist-get args :when)))
(not (eval pred t)))
(when-let (pred (plist-get args :unless))
(eval pred t)))))
use-package-defaults)
(dolist (module-files modules)
(cl-incf i)
(dolist (target (cdr module-files))
(let ((elc-file (byte-compile-dest-file target)))
(cl-incf
(if (and recompile-p (not (file-newer-than-file-p target elc-file)))
total-noop
(pcase (if (not (doom-file-cookie-p target "if" t))
'no-byte-compile
(unless (equal last-module (car module-files))
(print! (success "(% 3d/%d) Compiling %s %s module...")
i total-modules (caar module-files) (cdar module-files))
(setq last-module (car module-files)))
(if verbose-p
(byte-compile-file target)
(quiet! (byte-compile-file target))))
(`no-byte-compile
(print! (debug "(% 3d/%d) Ignored %s")
i total-modules (relpath target))
total-noop)
(`nil
(print! (error "(% 3d/%d) Failed to compile %s")
i total-modules (relpath target))
total-fail)
(_ total-ok)))))))
(print! (class (if (= total-fail 0) 'success 'warn)
"%s %d/%d file(s) (%d ignored)")
(if recompile-p "Recompiled" "Byte-compiled")
total-ok total-files
total-noop)
(= total-fail 0))
((debug error)
(print! (error "There were breaking errors.\n\n%s")
"Reverting changes...")
(signal 'doom-error (list 'byte-compile e))))))))
(defun doom-clean-byte-compiled-files ()
"Delete all the compiled elc files in your Emacs configuration and private
@@ -187,7 +179,8 @@ module. This does not include your byte-compiled, third party packages.'"
(print! (start "Cleaning .elc files"))
(print-group!
(cl-loop with default-directory = doom-emacs-dir
with success = nil
with success = 0
with esc = (if doom-debug-p "" "\033[1A")
for path
in (append (doom-glob doom-emacs-dir "*.elc")
(doom-files-in doom-private-dir :match "\\.elc$" :depth 1)
@@ -195,10 +188,10 @@ module. This does not include your byte-compiled, third party packages.'"
(doom-files-in doom-modules-dirs :match "\\.elc$" :depth 4))
if (file-exists-p path)
do (delete-file path)
and do (print! (success "Deleted %s") (relpath path))
and do (setq success t)
and do (print! (success "\033[KDeleted %s%s") (relpath path) esc)
and do (cl-incf success)
finally do
(print! (if success
(success "All elc files deleted")
(info "No elc files to clean"))))
(print! (if (> success 0)
(success "\033[K%d elc files deleted" success)
(info "\033[KNo elc files to clean"))))
t))

View File

@@ -1,10 +1,12 @@
;;; core/cli/debug.el -*- lexical-binding: t; -*-
(load! "autoload/debug" doom-core-dir)
;;
;;; Commands
(defcli! info
((format ["--json" "--md" "--lisp"] "What format to dump info into"))
((format ["--json" "--md" "--lisp"] "What format to dump info into"))
"Output system info in markdown for bug reports."
(pcase format
("--json"
@@ -24,6 +26,5 @@
(defcli! (version v) ()
"Show version information for Doom & Emacs."
:bare t
(doom/version)
nil)

View File

@@ -48,8 +48,8 @@ in."
;; REVIEW Refactor me
(print! (start "Checking your Emacs version..."))
(when EMACS27+
(warn! "Emacs %s detected. Emacs HEAD is unstable and may cause errors."
(when EMACS28+
(warn! "Emacs %s detected. Doom doesn't support Emacs 28/HEAD. It is unstable and may cause errors."
emacs-version))
(print! (start "Checking for Emacs config conflicts..."))
@@ -79,9 +79,8 @@ in."
(print! (start "Checking Doom Emacs..."))
(condition-case-unless-debug ex
(print-group!
(let ((doom-interactive-mode 'doctor))
(let ((doom-interactive-p 'doctor))
(doom-initialize 'force)
(doom-initialize-core)
(doom-initialize-modules))
(print! (success "Initialized Doom Emacs %s") doom-version)
@@ -100,13 +99,14 @@ in."
(when-let (size (ignore-errors (doom-file-size file doom-cache-dir)))
(when (> size 1048576) ; larger than 1mb
(warn! "%s is too large (%.02fmb). This may cause freezes or odd startup delays"
file (/ size 1024))
file (/ size 1024 1024.0))
(explain! "Consider deleting it from your system (manually)"))))
(unless (executable-find "rg")
(error! "Couldn't find the `rg' binary; this a hard dependecy for Doom, file searches may not work at all"))
(unless (ignore-errors (executable-find doom-projectile-fd-binary))
(warn! "Couldn't find the `fd' binary; project file searches will be slightly slower")
(unless (executable-find "rg")
(warn! "Couldn't find the `rg' binary either; project file searches will be even slower")))
(warn! "Couldn't find the `fd' binary; project file searches will be slightly slower"))
(require 'projectile)
(when (projectile-project-root "~")
@@ -123,11 +123,8 @@ in."
"both is rarely intentional; you should one or the other."))
;; Check for fonts
(if (not (fboundp 'find-font))
(progn
(warn! "Warning: unable to detect font")
(explain! "The `find-font' function is missing. This could indicate the incorrect "
"version of Emacs is being used!"))
(if (not (executable-find "fc-list"))
(warn! "Warning: unable to detect fonts because fontconfig isn't installed")
;; all-the-icons fonts
(when (and (pcase system-type
(`gnu/linux (concat (or (getenv "XDG_DATA_HOME")
@@ -136,14 +133,21 @@ in."
(`darwin "~/Library/Fonts/"))
(require 'all-the-icons nil t))
(with-temp-buffer
(insert (cdr (doom-call-process "fc-list")))
(dolist (font all-the-icons-font-names)
(if (save-excursion (re-search-backward font nil t))
(success! "Found font %s" font)
(print! (warn "Warning: couldn't find %S font") font)
(explain! "You can install it by running `M-x all-the-icons-install-fonts' within Emacs.\n\n"
"This could also mean you've installed them in non-standard locations, in which "
"case feel free to ignore this warning.")))))))
(let ((errors 0))
(cl-destructuring-bind (status . output)
(doom-call-process "fc-list" "" "file")
(if (not (zerop status))
(print! (error "There was an error running `fc-list'. Is fontconfig installed correctly?"))
(insert (cdr (doom-call-process "fc-list" "" "file")))
(dolist (font all-the-icons-font-names)
(if (save-excursion (re-search-backward font nil t))
(success! "Found font %s" font)
(print! (warn "Warning: couldn't find %S font") font)))
(when (> errors 0)
(explain! "Some all-the-icons fonts were missing.\n\n"
"You can install them by running `M-x all-the-icons-install-fonts' within Emacs.\n"
"This could also mean you've installed them in non-standard locations, in which "
"case feel free to ignore this warning.")))))))))
(print! (start "Checking for stale elc files in your DOOMDIR..."))
(when (file-directory-p doom-private-dir)
@@ -161,15 +165,17 @@ in."
(condition-case-unless-debug ex
(let ((doctor-file (doom-module-path (car key) (cdr key) "doctor.el"))
(packages-file (doom-module-path (car key) (cdr key) "packages.el")))
(cl-loop for name in (let (doom-packages
(cl-loop with doom-output-indent = 6
for name in (let (doom-packages
doom-disabled-packages)
(load packages-file 'noerror 'nomessage)
(mapcar #'car doom-packages))
unless (or (doom-package-get name :disable)
(eval (doom-package-get name :ignore))
(plist-member (doom-package-get name :recipe) :local-repo)
(doom-package-built-in-p name)
(doom-package-installed-p name))
do (print! (error "%s is not installed") name))
do (print! (error "Missing emacs package: %S") name))
(let ((inhibit-message t))
(load doctor-file 'noerror 'nomessage)))
(file-missing (error! "%s" (error-message-string ex)))

View File

@@ -1,26 +1,28 @@
;;; core/cli/env.el -*- lexical-binding: t; -*-
(defcli! env
((clear-p ["-c" "--clear"] "Clear and delete your envvar file")
(outputfile ["-o" PATH]
"Generate the envvar file at PATH. Note that envvar files that aren't in
`doom-env-file' won't be loaded automatically at startup. You will need to
load them manually from your private config with the `doom-load-envvars-file'
((allow ["-a" "--allow" regexp] "An envvar whitelist regexp")
(reject ["-r" "--reject" regexp] "An envvar blacklist regexp")
(clear-p ["-c" "--clear"] "Clear and delete your envvar file")
(outputfile ["-o" path]
"Generate the envvar file at PATH. Envvar files that aren't in
`doom-env-file' won't be loaded automatically at startup. You will need to load
them manually from your private config with the `doom-load-envvars-file'
function."))
"Creates or regenerates your envvars file.
The envvars file is created by scraping your (interactive) shell environment
into newline-delimited KEY=VALUE pairs. Typically by running '$SHELL -ic env'
(or '$SHELL -c set' on windows). Doom loads this file at startup (if it exists)
to ensure Emacs mirrors your shell environment (particularly to ensure PATH and
The envvars file is created by scraping the current shell environment into
newline-delimited KEY=VALUE pairs. Typically by running '$SHELL -ic env' (or
'$SHELL -c set' on windows). Doom loads this file at startup (if it exists) to
ensure Emacs mirrors your shell environment (particularly to ensure PATH and
SHELL are correctly set).
This is useful in cases where you cannot guarantee that Emacs (or the daemon)
will be launched from the correct environment (e.g. on MacOS or through certain
app launchers on Linux).
This file is automatically regenerated when you run this command or 'doom
refresh'. However, 'doom refresh' will only regenerate this file if it exists.
This file is automatically regenerated when you run this command or 'doom sync'.
However, 'doom sync' will only regenerate this file if it exists.
Why this over exec-path-from-shell?
@@ -36,96 +38,91 @@ Why this over exec-path-from-shell?
I'd rather it inherit your shell environment /correctly/ (and /completely/)
or not at all. It frontloads the debugging process rather than hiding it
until it you least want to deal with it."
until you least want to deal with it."
(let ((env-file (expand-file-name (or outputfile doom-env-file))))
(cond (clear-p
(unless (file-exists-p env-file)
(user-error! "%S does not exist to be cleared"
(path env-file)))
(delete-file env-file)
(print! (success "Successfully deleted %S")
(path env-file)))
(args
(user-error "I don't understand 'doom env %s'"
(string-join args " ")))
((doom-cli-reload-env-file 'force env-file)))))
(if (null clear-p)
(doom-cli-reload-env-file
'force env-file (list allow) (list reject))
(unless (file-exists-p env-file)
(user-error! "%S does not exist to be cleared"
(path env-file)))
(delete-file env-file)
(print! (success "Successfully deleted %S")
(path env-file)))))
;;
;; Helpers
(defvar doom-env-ignored-vars
(defvar doom-env-blacklist
'("^DBUS_SESSION_BUS_ADDRESS$"
"^GPG_AGENT_INFO$"
"^GPG_TTY$"
"^HOME$"
"^PS1$"
"^PWD$"
"^R?PROMPT$"
"^SSH_AGENT_PID$"
"^SSH_AUTH_SOCK$"
"^TERM$"
"^GPG_AGENT_INFO$" "^\\(SSH\\|GPG\\)_TTY$"
"^SSH_\\(AUTH_SOCK\\|AGENT_PID\\)$"
"^HOME$" "^PWD$" "^PS1$" "^R?PROMPT$" "^TERM$"
;; Doom envvars
"^DEBUG$"
"^INSECURE$"
"^YES$"
"^__")
"^DEBUG$" "^INSECURE$" "^YES$" "^__")
"Environment variables to not save in `doom-env-file'.
Each string is a regexp, matched against variable names to omit from
`doom-env-file'.")
(defun doom-cli-reload-env-file (&optional force-p env-file)
(defvar doom-env-whitelist '()
"A whitelist for envvars to save in `doom-env-file'.
This overrules `doom-env-ignored-vars'. Each string is a regexp, matched against
variable names to omit from `doom-env-file'.")
(defun doom-cli-reload-env-file (&optional force-p env-file whitelist blacklist)
"Generates `doom-env-file', if it doesn't exist (or if FORCE-P).
This scrapes the variables from your shell environment by running
`doom-env-executable' through `shell-file-name' with `doom-env-switches'. By
default, on Linux, this is '$SHELL -ic /usr/bin/env'. Variables in
`doom-env-ignored-vars' are removed."
(let ((env-file (if env-file
(expand-file-name env-file)
doom-env-file)))
(let ((env-file (if env-file (expand-file-name env-file) doom-env-file))
(process-environment doom--initial-process-environment))
(when (or force-p (not (file-exists-p env-file)))
(with-temp-file env-file
(setq-local coding-system-for-write 'utf-8)
(print! (start "%s envvars file at %S")
(if (file-exists-p env-file)
"Regenerating"
"Generating")
(path env-file))
(let ((process-environment doom--initial-process-environment))
(print! (info "Scraping shell environment"))
(print-group!
(when doom-interactive-mode
(user-error "'doom env' must be run on the command line, not an interactive session"))
(goto-char (point-min))
(insert
(concat
"# -*- mode: dotenv -*-\n"
(format "# Generated from a %s shell environent\n" shell-file-name)
"# ---------------------------------------------------------------------------\n"
"# This file was auto-generated by `doom env'. It contains a list of environment\n"
"# variables scraped from your default shell (excluding variables blacklisted\n"
"# in doom-env-ignored-vars).\n"
"#\n"
(if (file-equal-p env-file doom-env-file)
(concat "# It is NOT safe to edit this file. Changes will be overwritten next time you\n"
"# run 'doom refresh'. To create a safe-to-edit envvar file use:\n#\n"
"# doom env -o ~/.doom.d/myenv\n#\n"
"# And load it with (doom-load-envvars-file \"~/.doom.d/myenv\").\n")
(concat "# This file is safe to edit by hand, but needs to be loaded manually with:\n#\n"
"# (doom-load-envvars-file \"path/to/this/file\")\n#\n"
"# Use 'doom env -o path/to/this/file' to regenerate it."))
"# ---------------------------------------------------------------------------\n\n"))
;; We assume that this noninteractive session was spawned from the
;; user's interactive shell, therefore we just dump
;; `process-environment' to a file.
(dolist (env process-environment)
(if (cl-find-if (doom-rpartial #'string-match-p (car (split-string env "=")))
doom-env-ignored-vars)
(print! (info "Ignoring %s") env)
(insert env "\n")))
(print! (success "Successfully generated %S")
(path env-file))
t))))))
(print-group!
(when doom-interactive-p
(user-error "'doom env' must be run on the command line, not an interactive session"))
(goto-char (point-min))
(insert
(concat
"# -*- mode: sh -*-\n"
"# ---------------------------------------------------------------------------\n"
"# This file was auto-generated by `doom env'. It contains a list of environment\n"
"# variables scraped from your default shell (excluding variables blacklisted\n"
"# in doom-env-ignored-vars).\n"
"#\n"
(if (file-equal-p env-file doom-env-file)
(concat "# It is NOT safe to edit this file. Changes will be overwritten next time you\n"
"# run 'doom sync'. To create a safe-to-edit envvar file use:\n#\n"
"# doom env -o ~/.doom.d/myenv\n#\n"
"# And load it with (doom-load-envvars-file \"~/.doom.d/myenv\").\n")
(concat "# This file is safe to edit by hand, but remember to preserve the null bytes at\n"
"# the end of each line! needs to be loaded manually with:\n#\n"
"# (doom-load-envvars-file \"path/to/this/file\")\n#\n"
"# Use 'doom env -o path/to/this/file' to regenerate it."))
"# ---------------------------------------------------------------------------\n\0\n"))
;; We assume that this noninteractive session was spawned from the
;; user's interactive shell, therefore we just dump
;; `process-environment' to a file.
(dolist (env process-environment)
(if (cl-find-if (doom-rpartial #'string-match-p (car (split-string env "=")))
(remq nil (append blacklist doom-env-blacklist)))
(if (not (cl-find-if (doom-rpartial #'string-match-p (car (split-string env "=")))
(remq nil (append whitelist doom-env-whitelist))))
(print! (info "Ignoring %s") env)
(print! (info "Whitelisted %s") env)
(insert env "\0\n"))
(insert env "\0\n")))
(print! (success "Successfully generated %S")
(path env-file))
t)))))

View File

@@ -4,8 +4,7 @@
((noconfig-p ["--no-config"] "Don't create DOOMDIR or dummy files therein")
(noenv-p ["--no-env"] "Don't generate an envvars file (see 'doom help env')")
(noinstall-p ["--no-install"] "Don't auto-install packages")
(nofonts-p ["--no-fonts"] "Don't install (or prompt to install) all-the-icons fonts")
&rest _args)
(nofonts-p ["--no-fonts"] "Don't install (or prompt to install) all-the-icons fonts"))
"Installs and sets up Doom Emacs for the first time.
This command does the following:
@@ -24,7 +23,6 @@ DOOMDIR environment variable. e.g.
doom -p ~/.config/doom install
DOOMDIR=~/.config/doom doom install"
:bare t
(print! (green "Installing Doom Emacs!\n"))
(let ((default-directory (doom-path "~")))
;; Create `doom-private-dir'
@@ -37,34 +35,20 @@ DOOMDIR environment variable. e.g.
;; Create init.el, config.el & packages.el
(mapc (lambda (file)
(cl-destructuring-bind (filename . fn) file
(cl-destructuring-bind (filename . template) file
(if (file-exists-p! filename doom-private-dir)
(print! (warn "%s already exists, skipping") filename)
(print! (info "Creating %s%s") (relpath doom-private-dir) filename)
(with-temp-file (doom-path doom-private-dir filename)
(funcall fn))
(insert-file-contents template))
(print! (success "Done!")))))
'(("init.el" .
(lambda ()
(insert-file-contents (doom-path doom-emacs-dir "init.example.el"))))
("config.el" .
(lambda ()
(insert! ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n"
";; Place your private configuration here\n"
((relpath doom-private-dir)))))
("packages.el" .
(lambda ()
(insert! ";; -*- no-byte-compile: t; -*-\n;;; %spackages.el\n\n"
";;; Examples:\n"
";; (package! some-package)\n"
";; (package! another-package :recipe (:host github :repo \"username/repo\"))\n"
";; (package! builtin-package :disable t)\n"
((relpath doom-private-dir))))))))
`(("init.el" . ,(doom-path doom-emacs-dir "init.example.el"))
("config.el" . ,(doom-path doom-core-dir "templates/config.example.el"))
("packages.el" . ,(doom-path doom-core-dir "templates/packages.example.el")))))
;; In case no init.el was present the first time `doom-initialize-modules' was
;; called in core.el (e.g. on first install)
(doom-initialize 'force 'noerror)
(doom-initialize-modules)
(doom-initialize-modules 'force 'no-config)
;; Ask if user would like an envvar file generated
(if noenv-p
@@ -72,7 +56,7 @@ DOOMDIR environment variable. e.g.
(if (file-exists-p doom-env-file)
(print! (info "Envvar file already exists, skipping"))
(when (or doom-auto-accept
(y-or-n-p "Generate an env file? (see `doom help env` for details)"))
(y-or-n-p "Generate an envvar file? (see `doom help env` for details)"))
(doom-cli-reload-env-file 'force-p))))
;; Install Doom packages
@@ -82,21 +66,29 @@ DOOMDIR environment variable. e.g.
(doom-cli-packages-install))
(print! "Regenerating autoloads files")
(doom-cli-reload-autoloads nil 'force-p)
(doom-autoloads-reload)
(if nofonts-p
(print! (warn "Not installing fonts, as requested"))
(when (or doom-auto-accept
(y-or-n-p "Download and install all-the-icon's fonts?"))
(require 'all-the-icons)
(let ((window-system (cond (IS-MAC 'ns)
(IS-LINUX 'x))))
(all-the-icons-install-fonts 'yes))))
(cond (nofonts-p)
(IS-WINDOWS
(print! (warn "Doom cannot install all-the-icons' fonts on Windows!\n"))
(print-group!
(print!
(concat "You'll have to do so manually:\n\n"
" 1. Launch Doom Emacs\n"
" 2. Execute 'M-x all-the-icons-install-fonts' to download the fonts\n"
" 3. Open the download location in windows explorer\n"
" 4. Open each font file to install them"))))
((or doom-auto-accept
(y-or-n-p "Download and install all-the-icon's fonts?"))
(require 'all-the-icons)
(let ((window-system (cond (IS-MAC 'ns)
(IS-LINUX 'x))))
(all-the-icons-install-fonts 'yes))))
(when (file-exists-p "~/.emacs")
(print! (warn "A ~/.emacs file was detected. This conflicts with Doom and should be deleted!")))
(print! (success "\nFinished! Doom is ready to go!\n"))
(with-temp-buffer
(doom-template-insert "QUICKSTART_INTRO")
(print! (buffer-string)))))
(insert-file-contents (doom-glob doom-core-dir "templates/QUICKSTART_INTRO"))
(print! "%s" (buffer-string)))))

View File

@@ -1,7 +1,8 @@
;; -*- no-byte-compile: t; -*-
;;; core/cli/packages.el
(defcli! (update u) ()
(defcli! (update u)
((discard-p ["--discard"] "All local changes to packages are discarded"))
"Updates packages.
This works by fetching all installed package repos and checking the distance
@@ -10,10 +11,10 @@ between HEAD and FETCH_HEAD. This can take a while.
This excludes packages whose `package!' declaration contains a non-nil :freeze
or :ignore property."
(straight-check-all)
(doom-cli-reload-core-autoloads)
(when (doom-cli-packages-update)
(doom-cli-reload-package-autoloads 'force-p))
t)
(let ((doom-auto-discard discard-p))
(when (doom-cli-packages-update)
(doom-autoloads-reload))
t))
(defcli! (build b)
((rebuild-p ["-r"] "Only rebuild packages that need rebuilding"))
@@ -23,7 +24,7 @@ This ensures that all needed files are symlinked from their package repo and
their elisp files are byte-compiled. This is especially necessary if you upgrade
Emacs (as byte-code is generally not forward-compatible)."
(when (doom-cli-packages-build (not rebuild-p))
(doom-cli-reload-package-autoloads 'force-p))
(doom-autoloads-reload))
t)
(defcli! (purge p)
@@ -46,7 +47,7 @@ list remains lean."
(not norepos-p)
(not nobuilds-p)
regraft-p)
(doom-cli-reload-package-autoloads 'force-p))
(doom-autoloads-reload))
t)
;; (defcli! rollback () ; TODO doom rollback
@@ -57,144 +58,248 @@ list remains lean."
;;
;;; Library
(defun doom--same-commit-p (abbrev-ref ref)
(and (stringp abbrev-ref)
(stringp ref)
(string-match-p (concat "^" (regexp-quote abbrev-ref))
ref)))
(defun doom--abbrev-commit (commit &optional full)
(if full commit (substring commit 0 7)))
(defun doom--commit-log-between (start-ref end-ref)
(and (straight--call
"git" "log" "--oneline" "--no-merges"
"-n" "25" end-ref (concat "^" (regexp-quote start-ref)))
(straight--process-get-output)))
(defun doom--barf-if-incomplete-packages ()
(let ((straight-safe-mode t))
(condition-case _ (straight-check-all)
(error (user-error "Package state is incomplete. Run 'doom sync' first")))))
(defmacro doom--with-package-recipes (recipes binds &rest body)
(declare (indent 2))
(let ((recipe-var (make-symbol "recipe"))
(recipes-var (make-symbol "recipes")))
`(let* ((,recipes-var ,recipes)
(built ())
(straight-use-package-pre-build-functions
(cons (lambda (pkg &rest _) (cl-pushnew pkg built :test #'equal))
straight-use-package-pre-build-functions)))
(dolist (,recipe-var ,recipes-var (nreverse built))
(cl-block nil
(straight--with-plist (append (list :recipe ,recipe-var) ,recipe-var)
,(doom-enlist binds)
,@body))))))
(defvar doom--cli-updated-recipes nil)
(defun doom--cli-recipes-update ()
"Updates straight and recipe repos."
(unless doom--cli-updated-recipes
(straight--make-build-cache-available)
(print! (start "Updating recipe repos..."))
(print-group!
(doom--with-package-recipes
(delq
nil (mapcar (doom-rpartial #'gethash straight--repo-cache)
(mapcar #'symbol-name straight-recipe-repositories)))
(recipe package type local-repo)
(let ((esc (unless doom-debug-p "\033[1A"))
(ref (straight-vc-get-commit type local-repo))
newref output)
(print! (start "\033[KUpdating recipes for %s...%s") package esc)
(when (straight-vc-fetch-from-remote recipe)
(setq output (straight--process-get-output))
(straight-merge-package package)
(unless (equal ref (setq newref (straight-vc-get-commit type local-repo)))
(print! (success "\033[K%s updated (%s -> %s)")
package
(doom--abbrev-commit ref)
(doom--abbrev-commit newref))
(unless (string-empty-p output)
(print-group! (print! (info "%s" output)))))))))
(setq straight--recipe-lookup-cache (make-hash-table :test #'eq)
doom--cli-updated-recipes t)))
(defun doom-cli-packages-install ()
"Installs missing packages.
This function will install any primary package (i.e. a package with a `package!'
declaration) or dependency thereof that hasn't already been."
(print! (start "Installing & building packages..."))
(print-group!
(let ((n 0))
(dolist (package (hash-table-keys straight--recipe-cache))
(straight--with-plist (gethash package straight--recipe-cache)
(local-repo)
(let ((existed-p (file-directory-p (straight--repos-dir package))))
(condition-case-unless-debug e
(and (straight-use-package (intern package) nil nil (make-string (1- (or doom-format-indent 1)) 32))
(not existed-p)
(file-directory-p (straight--repos-dir package))
(cl-incf n))
(error
(signal 'doom-package-error
(list e (straight--process-get-output))))))))
(if (= n 0)
(ignore (print! (success "No packages need to be installed")))
(print! (success "Installed & built %d packages") n)
t))))
(doom-initialize-packages)
(print! (start "Installing packages..."))
(let ((pinned (doom-package-pinned-list)))
(print-group!
(if-let (built
(doom--with-package-recipes (doom-package-recipe-list)
(recipe package type local-repo)
(unless (file-directory-p (straight--repos-dir local-repo))
(doom--cli-recipes-update))
(condition-case-unless-debug e
(let ((straight-use-package-pre-build-functions
(cons (lambda (pkg &rest _)
(when-let (commit (cdr (assoc pkg pinned)))
(print! (info "Checked out %s") commit)))
straight-use-package-pre-build-functions)))
(straight-use-package (intern package)))
(error
(signal 'doom-package-error (list package e))))))
(print! (success "Installed %d packages")
(length built))
(print! (info "No packages need to be installed"))
nil))))
(defun doom-cli-packages-build (&optional force-p)
"(Re)build all packages."
(doom-initialize-packages)
(print! (start "(Re)building %spackages...") (if force-p "all " ""))
(print-group!
(let ((n 0))
(if force-p
(let ((straight--packages-to-rebuild :all)
(straight--packages-not-to-rebuild (make-hash-table :test #'equal)))
(dolist (package (hash-table-keys straight--recipe-cache))
(straight-use-package
(intern package) nil (lambda (_) (cl-incf n) nil)
(make-string (1- (or doom-format-indent 1)) 32))))
(dolist (recipe (hash-table-values straight--recipe-cache))
(straight--with-plist recipe (package local-repo no-build)
(unless (or no-build (null local-repo))
;; REVIEW We do these modification checks manually because
;; Straight's checks seem to miss stale elc files. Need
;; more tests to confirm this.
(when (or (ignore-errors
(gethash package straight--packages-to-rebuild))
(gethash package straight--cached-package-modifications)
(not (file-directory-p (straight--build-dir package)))
(cl-loop for file
in (doom-files-in (straight--build-dir package)
:match "\\.el$"
:full t)
for elc-file = (byte-compile-dest-file file)
if (and (file-exists-p elc-file)
(file-newer-than-file-p file elc-file))
return t))
(let ((straight-use-package-pre-build-functions
straight-use-package-pre-build-functions))
(add-hook 'straight-use-package-pre-build-functions
(lambda (&rest _) (cl-incf n)))
(let ((straight--packages-to-rebuild :all)
(straight--packages-not-to-rebuild (make-hash-table :test #'equal)))
(straight-use-package
(intern package) nil nil
(make-string (or doom-format-indent 0) 32)))
(straight--byte-compile-package recipe)
(dolist (dep (straight--get-dependencies package))
(when-let (recipe (gethash dep straight--recipe-cache))
(straight--byte-compile-package recipe)))))))))
(if (= n 0)
(ignore (print! (success "No packages need rebuilding")))
(doom--finalize-straight)
(print! (success "Rebuilt %d package(s)" n))
t))))
(let ((straight-check-for-modifications
(when (file-directory-p (straight--modified-dir))
'(find-when-checking)))
(straight--allow-find
(and straight-check-for-modifications
(executable-find straight-find-executable)
t))
(straight--packages-not-to-rebuild
(or straight--packages-not-to-rebuild (make-hash-table :test #'equal)))
(straight--packages-to-rebuild
(or (if force-p :all straight--packages-to-rebuild)
(make-hash-table :test #'equal)))
(recipes (doom-package-recipe-list)))
(unless force-p
(straight--make-build-cache-available))
(if-let (built
(doom--with-package-recipes recipes (package local-repo recipe)
(unless force-p
;; Ensure packages with outdated files/bytecode are rebuilt
(let ((build-dir (straight--build-dir package))
(repo-dir (straight--repos-dir local-repo)))
(and (or (file-newer-than-file-p repo-dir build-dir)
(file-exists-p (straight--modified-dir (or local-repo package)))
;; Doesn't make sense to compare el and elc files
;; when the former isn't a symlink to their source.
(when straight-use-symlinks
(cl-loop for file
in (doom-files-in build-dir :match "\\.el$" :full t)
for elc-file = (byte-compile-dest-file file)
if (and (file-exists-p elc-file)
(file-newer-than-file-p file elc-file))
return t)))
(not (plist-get recipe :no-build))
(puthash package t straight--packages-to-rebuild))))
(straight-use-package (intern package))))
(print! (success "Rebuilt %d package(s)") (length built))
(print! (success "No packages need rebuilding"))
nil))))
(defun doom-cli-packages-update ()
"Updates packages."
(print! (start "Updating packages (this may take a while)..."))
;; TODO Refactor me
(let ((straight--repos-dir (straight--repos-dir))
(straight--packages-to-rebuild (make-hash-table :test #'equal))
(total (hash-table-count straight--repo-cache))
(i 1)
errors)
(print-group!
(dolist (recipe (hash-table-values straight--repo-cache))
(straight--with-plist recipe (package type local-repo)
(doom-initialize-packages)
(doom--barf-if-incomplete-packages)
(let* ((repo-dir (straight--repos-dir))
(pinned (doom-package-pinned-list))
(recipes (doom-package-recipe-list))
(packages-to-rebuild (make-hash-table :test 'equal))
(repos-to-rebuild (make-hash-table :test 'equal))
(total (length recipes))
(esc (unless doom-debug-p "\033[1A"))
(i 0)
errors)
(when recipes
(doom--cli-recipes-update))
(print! (start "Updating packages (this may take a while)..."))
(doom--with-package-recipes recipes (recipe package type local-repo)
(cl-incf i)
(print-group!
(unless (straight--repository-is-available-p recipe)
(print! (error "(%d/%d) Couldn't find local repo for %s") i total package)
(cl-return))
(when (gethash local-repo repos-to-rebuild)
(puthash package t packages-to-rebuild)
(print! (success "(%d/%d) %s was updated indirectly (with %s)") i total package local-repo)
(cl-return))
(let ((default-directory (straight--repos-dir local-repo)))
(unless (file-in-directory-p default-directory repo-dir)
(print! (warn "(%d/%d) Skipping %s because it is local") i total package)
(cl-return))
(when (eq type 'git)
(unless (file-exists-p ".git")
(error "%S is not a valid repository" package)))
(condition-case-unless-debug e
(let ((default-directory (straight--repos-dir local-repo)))
(if (not (file-in-directory-p default-directory straight--repos-dir))
(print! (warn "[%d/%d] Skipping %s because it is local")
i total package)
(let ((commit (straight-vc-get-commit type local-repo)))
(if (not (straight-vc-fetch-from-remote recipe))
(print! (warn "\033[K(%d/%d) Failed to fetch %s" i total package))
(let ((output (straight--process-get-output)))
(let ((ref (straight-vc-get-commit type local-repo))
(target-ref
(cdr (or (assoc local-repo pinned)
(assoc package pinned))))
output)
(or (cond
((not (stringp target-ref))
(print! (start "\033[K(%d/%d) Fetching %s...%s") i total package esc)
(when (straight-vc-fetch-from-remote recipe)
(setq output (straight--process-get-output))
(straight-merge-package package)
(let ((newcommit (straight-vc-get-commit type local-repo)))
(if (string= commit newcommit)
(print! (start "\033[K(%d/%d) %s is up-to-date\033[1A") i total package)
(ignore-errors
(delete-directory (straight--build-dir package) 'recursive))
(puthash package t straight--packages-to-rebuild)
(print! (info "\033[K(%d/%d) Updating %s...") i total package)
(unless (string-empty-p output)
(print-group!
(print! (info "%s") output)
(when (eq type 'git)
(straight--call "git" "log" "--oneline" newcommit (concat "^" commit))
(print-group!
(print! "%s" (straight--process-get-output))))))
(print! (success "(%d/%d) %s updated (%s -> %s)") i total package
(substring commit 0 7)
(substring newcommit 0 7))))))))
(cl-incf i))
(setq target-ref (straight-vc-get-commit type local-repo))
(or (not (doom--same-commit-p target-ref ref))
(cl-return))))
((doom--same-commit-p target-ref ref)
(print! (info "\033[K(%d/%d) %s is up-to-date...%s") i total package esc)
(cl-return))
((if (straight-vc-commit-present-p recipe target-ref)
(print! (start "\033[K(%d/%d) Checking out %s (%s)...%s")
i total package (doom--abbrev-commit target-ref) esc)
(print! (start "\033[K(%d/%d) Fetching %s...%s") i total package esc)
(and (straight-vc-fetch-from-remote recipe)
(straight-vc-commit-present-p recipe target-ref)))
(straight-vc-check-out-commit recipe target-ref)
(or (not (eq type 'git))
(setq output (doom--commit-log-between ref target-ref)))
(doom--same-commit-p target-ref (straight-vc-get-commit type local-repo)))
((print! (start "\033[K(%d/%d) Re-cloning %s...") i total local-repo esc)
(let ((repo (straight--repos-dir local-repo))
(straight-vc-git-default-clone-depth 'full))
(delete-directory repo 'recursive)
(print-group!
(straight-use-package (intern package) nil 'no-build))
(prog1 (file-directory-p repo)
(or (not (eq type 'git))
(setq output (doom--commit-log-between ref target-ref)))))))
(progn
(print! (warn "\033[K(%d/%d) Failed to fetch %s")
i total local-repo)
(unless (string-empty-p output)
(print-group! (print! (info "%s" output))))
(cl-return)))
(puthash local-repo t repos-to-rebuild)
(puthash package t packages-to-rebuild)
(unless (string-empty-p output)
(print! (start "\033[K(%d/%d) Updating %s...") i total local-repo)
(print-group! (print! "%s" (indent 2 output))))
(print! (success "\033[K(%d/%d) %s updated (%s -> %s)")
i total local-repo
(doom--abbrev-commit ref)
(doom--abbrev-commit target-ref)))
(user-error
(signal 'user-error (error-message-string e)))
(error
(print! (warn "(%d/%d) Encountered error with %s" i total package))
(print-group!
(print! (error "%s" e))
(print-group! (print! (info "%s" (straight--process-get-output)))))
(push package errors)))))
(signal 'doom-package-error (list package e)))))))
(print-group!
(princ "\033[K")
(when errors
(print! (error "There were %d errors, the offending packages are: %s")
(length errors) (string-join errors ", ")))
(if (hash-table-empty-p straight--packages-to-rebuild)
(ignore
(print! (success "All %d packages are up-to-date")
(hash-table-count straight--repo-cache)))
(let ((count (hash-table-count straight--packages-to-rebuild))
(packages (hash-table-keys straight--packages-to-rebuild)))
(sort packages #'string-lessp)
(doom--finalize-straight)
(doom-cli-packages-build)
(print! (success "Updated %d package(s)") count))
(if (hash-table-empty-p packages-to-rebuild)
(ignore (print! (success "All %d packages are up-to-date") total))
(straight--transaction-finalize)
(let ((default-directory (straight--build-dir)))
(mapc (doom-rpartial #'delete-directory 'recursive)
(hash-table-keys packages-to-rebuild)))
(print! (success "Updated %d package(s)")
(hash-table-count packages-to-rebuild))
(doom-cli-packages-build)
t))))
@@ -209,30 +314,38 @@ declaration) or dependency thereof that hasn't already been."
(defun doom--cli-packages-purge-builds (builds)
(if (not builds)
(progn (print! (info "No builds to purge"))
0)
(length
(delq nil (mapcar #'doom--cli-packages-purge-build builds)))))
(prog1 0
(print! (info "No builds to purge")))
(print! (start "Purging straight builds..." (length builds)))
(print-group!
(length
(delq nil (mapcar #'doom--cli-packages-purge-build builds))))))
(defun doom--cli-packages-regraft-repo (repo)
(cl-defun doom--cli-packages-regraft-repo (repo)
(let ((default-directory (straight--repos-dir repo)))
(if (not (file-directory-p ".git"))
(ignore (print! (warn "\033[Krepos/%s is not a git repo, skipping" repo)))
(let ((before-size (doom-directory-size default-directory)))
(straight--call "git" "reset" "--hard")
(straight--call "git" "clean" "-ffd")
(if (not (car (straight--call "git" "replace" "--graft" "HEAD")))
(print! (info "\033[Krepos/%s is already compact\033[1A" repo))
(straight--call "git" "gc")
(print! (success "\033[KRegrafted repos/%s (from %0.1fKB to %0.1fKB)")
repo before-size (doom-directory-size default-directory))
(print-group! (print! "%s" (straight--process-get-output)))))
(unless (file-directory-p ".git")
(print! (warn "\033[Krepos/%s is not a git repo, skipping" repo))
(cl-return))
(unless (file-in-directory-p default-directory straight-base-dir)
(print! (warn "\033[KSkipping repos/%s because it is local" repo))
(cl-return))
(let ((before-size (doom-directory-size default-directory)))
(straight--call "git" "reset" "--hard")
(straight--call "git" "clean" "-ffd")
(if (not (car (straight--call "git" "replace" "--graft" "HEAD")))
(print! (info "\033[Krepos/%s is already compact\033[1A" repo))
(straight--call "git" "reflog" "expire" "--expire=all" "--all")
(straight--call "git" "gc" "--prune=now")
(print! (success "\033[KRegrafted repos/%s (from %0.1fKB to %0.1fKB)")
repo before-size (doom-directory-size default-directory))
(print-group! (print! "%s" (straight--process-get-output))))
t)))
(defun doom--cli-packages-regraft-repos (repos)
(if (not repos)
(progn (print! (info "No repos to regraft"))
0)
(prog1 0
(print! (info "No repos to regraft")))
(print! (start "Regrafting %d repos..." (length repos)))
(let ((before-size (doom-directory-size (straight--repos-dir))))
(print-group!
(prog1 (delq nil (mapcar #'doom--cli-packages-regraft-repo repos))
@@ -245,8 +358,7 @@ declaration) or dependency thereof that hasn't already been."
(defun doom--cli-packages-purge-repo (repo)
(let ((repo-dir (straight--repos-dir repo)))
(delete-directory repo-dir 'recursive)
(ignore-errors
(delete-file (straight--modified-file repo)))
(delete-file (straight--modified-file repo))
(if (file-directory-p repo-dir)
(ignore (print! (error "Failed to purge repos/%s" repo)))
(print! (success "Purged repos/%s" repo))
@@ -254,24 +366,31 @@ declaration) or dependency thereof that hasn't already been."
(defun doom--cli-packages-purge-repos (repos)
(if (not repos)
(progn (print! (info "No repos to purge"))
0)
(length
(delq nil (mapcar #'doom--cli-packages-purge-repo repos)))))
(prog1 0
(print! (info "No repos to purge")))
(print! (start "Purging straight repositories..."))
(print-group!
(length
(delq nil (mapcar #'doom--cli-packages-purge-repo repos))))))
(defun doom--cli-packages-purge-elpa ()
(unless (bound-and-true-p package--initialized)
(package-initialize))
(let ((packages (cl-loop for (package desc) in package-alist
for dir = (package-desc-dir desc)
if (file-in-directory-p dir package-user-dir)
collect (cons package dir))))
(if (not package-alist)
(progn (print! (info "No ELPA packages to purge"))
0)
(mapc (doom-rpartial #'delete-directory 'recursive)
(mapcar #'cdr packages))
(length packages))))
(require 'core-packages)
(let ((dirs (doom-files-in package-user-dir :type t :depth 0)))
(if (not dirs)
(prog1 0
(print! (info "No ELPA packages to purge")))
(print! (start "Purging ELPA packages..."))
(dolist (path dirs (length dirs))
(condition-case e
(print-group!
(if (file-directory-p path)
(delete-directory path 'recursive)
(delete-file path))
(print! (success "Deleted %s") (filename path)))
(error
(print! (error "Failed to delete %s because: %s")
(filename path)
e)))))))
(defun doom-cli-packages-purge (&optional elpa-p builds-p repos-p regraft-repos-p)
"Auto-removes orphaned packages and repos.
@@ -282,36 +401,35 @@ a `package!' declaration) or isn't depended on by another primary package.
If BUILDS-P, include straight package builds.
If REPOS-P, include straight repos.
If ELPA-P, include packages installed with package.el (M-x package-install)."
(print! (start "Searching for orphaned packages to purge (for the emperor)..."))
(doom-initialize-packages)
(doom--barf-if-incomplete-packages)
(print! (start "Purging orphaned packages (for the emperor)..."))
(cl-destructuring-bind (&optional builds-to-purge repos-to-purge repos-to-regraft)
(let ((rdirs (straight--directory-files (straight--repos-dir) nil nil 'sort))
(bdirs (straight--directory-files (straight--build-dir) nil nil 'sort)))
(list (cl-remove-if (doom-rpartial #'gethash straight--profile-cache)
bdirs)
(cl-remove-if (doom-rpartial #'straight--checkhash straight--repo-cache)
rdirs)
(cl-remove-if-not (doom-rpartial #'straight--checkhash straight--repo-cache)
rdirs)))
(let (success)
(print-group!
(if (not builds-p)
(print! (info "Skipping builds"))
(and (/= 0 (doom--cli-packages-purge-builds builds-to-purge))
(setq success t)
(straight-prune-build-cache)))
(if (not elpa-p)
(print! (info "Skipping elpa packages"))
(and (/= 0 (doom--cli-packages-purge-elpa))
(setq success t)))
(if (not repos-p)
(print! (info "Skipping repos"))
(and (/= 0 (doom--cli-packages-purge-repos repos-to-purge))
(setq success t)))
(if (not regraft-repos-p)
(print! (info "Skipping regrafting"))
(print! (start "Regrafting %d repos..." (length repos-to-regraft)))
(and (doom--cli-packages-regraft-repos repos-to-regraft)
(setq success t)))
(when success
(doom--finalize-straight)
t)))))
(let ((rdirs
(and (or repos-p regraft-repos-p)
(straight--directory-files (straight--repos-dir) nil nil 'sort))))
(list (when builds-p
(seq-remove (doom-rpartial #'gethash straight--profile-cache)
(straight--directory-files (straight--build-dir) nil nil 'sort)))
(when repos-p
(seq-remove (doom-rpartial #'straight--checkhash straight--repo-cache)
rdirs))
(when regraft-repos-p
(seq-filter (doom-rpartial #'straight--checkhash straight--repo-cache)
rdirs))))
(print-group!
(delq
nil (list
(if (not builds-p)
(ignore (print! (info "Skipping builds")))
(and (/= 0 (doom--cli-packages-purge-builds builds-to-purge))
(straight-prune-build-cache)))
(if (not elpa-p)
(ignore (print! (info "Skipping elpa packages")))
(/= 0 (doom--cli-packages-purge-elpa)))
(if (not repos-p)
(ignore (print! (info "Skipping repos")))
(/= 0 (doom--cli-packages-purge-repos repos-to-purge)))
(if (not regraft-repos-p)
(ignore (print! (info "Skipping regrafting")))
(doom--cli-packages-regraft-repos repos-to-regraft)))))))

View File

@@ -50,9 +50,9 @@
(require 'core-cli)
(doom-initialize 'force 'noerror)
(doom-initialize-modules)
(doom-cli-reload-core-autoloads 'force)
(doom-cli-reload-core-autoloads)
(when (doom-cli-packages-install)
(doom-cli-reload-package-autoloads 'force)))))
(doom-cli-reload-package-autoloads)))))
(unless (zerop status)
(error "Failed to bootstrap unit tests"))))
(with-temp-buffer

View File

@@ -1,7 +1,8 @@
;;; core/cli/upgrade.el -*- lexical-binding: t; -*-
(defcli! (upgrade up)
((force-p ["-f" "--force"]))
((force-p ["-f" "--force"] "Discard local changes to Doom and packages, and upgrade anyway")
(packages-only-p ["-p" "--packages"] "Only upgrade packages, not Doom"))
"Updates Doom and packages.
This requires that ~/.emacs.d is a git repo, and is the equivalent of the
@@ -10,18 +11,22 @@ following shell commands:
cd ~/.emacs.d
git pull --rebase
bin/doom clean
bin/doom refresh
bin/doom sync
bin/doom update"
:bare t
(if (delq
nil (list
(doom-cli-upgrade doom-auto-accept force-p)
(doom-cli-execute "refresh")
(when (doom-cli-packages-update)
(doom-cli-reload-package-autoloads 'force)
t)))
(print! (success "Done! Restart Emacs for changes to take effect."))
(print! "Nothing to do. Doom is up-to-date!")))
(let ((doom-auto-discard force-p))
(cond
(packages-only-p
(doom-cli-execute "sync" '("-u"))
(print! (success "Finished upgrading Doom Emacs")))
((doom-cli-upgrade doom-auto-accept doom-auto-discard)
;; Reload Doom's CLI & libraries, in case there were any upstream changes.
;; Major changes will still break, however
(print! (info "Reloading Doom Emacs"))
(doom-cli-execute-after "doom" "upgrade" "-p" (if force-p "-f")))
((print! "Nothing to do. Doom is up-to-date!")))))
;;
@@ -47,7 +52,13 @@ following shell commands:
process-file-side-effects)
(print! (start "Preparing to upgrade Doom Emacs and its packages..."))
(let* ((branch (vc-git--symbolic-ref doom-emacs-dir))
(let* (;; git name-rev may return BRANCH~X for detached HEADs and fully
;; qualified refs in some other cases, so an effort to strip out all
;; but the branch name is necessary. git symbolic-ref (or
;; `vc-git--symbolic-ref') won't work; it can't deal with submodules.
(branch (replace-regexp-in-string
"^\\(?:[^/]+/[^/]+/\\)?\\(.+\\)\\(?:~[0-9]+\\)?$" "\\1"
(cdr (doom-call-process "git" "name-rev" "--name-only" "HEAD"))))
(target-remote (format "%s/%s" doom-repo-remote branch)))
(unless branch
(error! (if (file-exists-p! ".git" doom-emacs-dir)
@@ -103,11 +114,10 @@ following shell commands:
(print! (start "Upgrading Doom Emacs..."))
(print-group!
(doom-clean-byte-compiled-files)
(if (and (zerop (car (doom-call-process "git" "reset" "--hard" target-remote)))
(equal (vc-git--rev-parse "HEAD") new-rev))
(print! (info "%s") (cdr result))
(unless (and (zerop (car (doom-call-process "git" "reset" "--hard" target-remote)))
(equal (vc-git--rev-parse "HEAD") new-rev))
(error "Failed to check out %s" (substring new-rev 0 10)))
(print! (success "Finished upgrading Doom Emacs")))
t)))))
(print! (info "%s") (cdr result))
t))))))
(ignore-errors
(doom-call-process "git" "remote" "remove" doom-repo-remote))))))

View File

@@ -2,14 +2,21 @@
(require 'seq)
;; Eagerly load these libraries because we may be in a session that hasn't been
;; fully initialized (e.g. where autoloads files haven't been generated or
;; `load-path' populated).
(load! "autoload/cli")
(load! "autoload/debug")
(load! "autoload/files")
(load! "autoload/format")
(load! "autoload/process")
(load! "autoload/plist")
(load! "autoload/files")
(load! "autoload/output")
;; Create all our core directories to quell file errors
(mapc (doom-rpartial #'make-directory 'parents)
(list doom-local-dir
doom-etc-dir
doom-cache-dir))
;; Ensure straight and the bare minimum is ready to go
(require 'core-modules)
(require 'core-packages)
(doom-initialize-core-packages)
;;
@@ -20,7 +27,16 @@
commands like `doom-cli-packages-install', `doom-cli-packages-update' and
`doom-packages-autoremove'.")
(defvar doom--cli-p nil)
(defvar doom-auto-discard (getenv "FORCE")
"If non-nil, discard all local changes while updating.")
(defvar doom-cli-file "cli"
"The basename of CLI config files for modules.
These are loaded when a Doom's CLI starts up. There users and modules can define
additional CLI commands, or reconfigure existing ones to better suit their
purpose.")
(defvar doom--cli-commands (make-hash-table :test 'equal))
(defvar doom--cli-groups (make-hash-table :test 'equal))
(defvar doom--cli-group nil)
@@ -63,9 +79,10 @@ commands like `doom-cli-packages-install', `doom-cli-packages-update' and
(defun doom--cli-process (cli args)
(let* ((args (copy-sequence args))
(arglist (copy-sequence (doom-cli-arglist cli)))
(expected (or (cl-position-if (doom-rpartial #'memq cl--lambda-list-keywords)
arglist)
(length arglist)))
(expected
(or (cl-position-if (doom-rpartial #'memq cl--lambda-list-keywords)
arglist)
(length arglist)))
(got 0)
restvar
rest
@@ -146,9 +163,27 @@ Executes a cli defined with `defcli!' with the name or alias specified by
COMMAND, and passes ARGS to it."
(if-let (cli (doom-cli-get command))
(funcall (doom-cli-fn cli)
(doom--cli-process cli args))
(doom--cli-process cli (remq nil args)))
(user-error "Couldn't find any %S command" command)))
(defun doom-cli-execute-after (&rest args)
"Execute shell command ARGS after this CLI session quits.
This is particularly useful when the capabilities of Emacs' batch terminal are
insufficient (like opening an instance of Emacs, or reloading Doom after a 'doom
upgrade')."
(let ((post-script (concat doom-local-dir ".doom.sh")))
(with-temp-file post-script
(insert "#!/usr/bin/env sh\n"
"rm -f " (prin1-to-string post-script) "\n"
"exec " (mapconcat #'shell-quote-argument (remq nil args) " ")
"\n"))
(let* ((current-mode (file-modes post-script))
(add-mode (logand ?\111 (default-file-modes))))
(or (/= (logand ?\111 current-mode) 0)
(zerop add-mode)
(set-file-modes post-script (logior current-mode add-mode))))))
(defmacro defcli! (name speclist &optional docstring &rest body)
"Defines a CLI command.
@@ -195,10 +230,6 @@ BODY will be run when this dispatcher is called."
for optsym = (if (listp opt) (car opt) opt)
unless (memq optsym cl--lambda-list-keywords)
collect (list optsym `(cdr (assq ',optsym --alist--))))
,@(unless (plist-get plist :bare)
'((unless doom-init-p
(doom-initialize 'force 'noerror)
(doom-initialize-modules))))
,@body)))
doom--cli-commands)
(when aliases
@@ -213,19 +244,142 @@ BODY will be run when this dispatcher is called."
,@body))
;;
;;; straight.el hacks
;; Straight was designed primarily for interactive use, in an interactive Emacs
;; session, but Doom does its package management in the terminal. Some things
;; must be modified get straight to behave and improve its UX for our users.
(defvar doom--straight-discard-options
'(("has diverged from"
. "^Reset [^ ]+ to branch")
("but recipe specifies a URL of"
. "Delete remote \"[^\"]+\", re-create it with correct URL")
("has a merge conflict:"
. "^Abort merge$")
("has a dirty worktree:"
. "^Discard changes$")
("^In repository "
. "^Reset branch \\|^Delete remote [^,]+, re-create it with correct URL"))
"A list of regexps, mapped to regexps.
Their CAR is tested against the prompt, and CDR is tested against the presented
option, and is used by `straight-vc-git--popup-raw' to select which option to
recommend.
It may not be obvious to users what they should do for some straight prompts,
so Doom will recommend the one that reverts a package back to its (or target)
original state.")
;; HACK Remove dired & magit options from prompt, since they're inaccessible in
;; noninteractive sessions.
(advice-add #'straight-vc-git--popup-raw :override #'straight--popup-raw)
;; HACK Replace GUI popup prompts (which hang indefinitely in tty Emacs) with
;; simple prompts.
(defadvice! doom--straight-fallback-to-y-or-n-prompt-a (orig-fn &optional prompt)
:around #'straight-are-you-sure
(or doom-auto-accept
(if doom-interactive-p
(funcall orig-fn prompt)
(y-or-n-p (format! "%s" (or prompt ""))))))
(defun doom--straight-recommended-option-p (prompt option)
(cl-loop for (prompt-re . opt-re) in doom--straight-discard-options
if (string-match-p prompt-re prompt)
return (string-match-p opt-re option)))
(defadvice! doom--straight-fallback-to-tty-prompt-a (orig-fn prompt actions)
"Modifies straight to prompt on the terminal when in noninteractive sessions."
:around #'straight--popup-raw
(if doom-interactive-p
(funcall orig-fn prompt actions)
(let ((doom--straight-discard-options doom--straight-discard-options))
;; We can't intercept C-g, so no point displaying any options for this key
;; when C-c is the proper way to abort batch Emacs.
(delq! "C-g" actions 'assoc)
;; HACK These are associated with opening dired or magit, which isn't
;; possible in tty Emacs, so...
(delq! "e" actions 'assoc)
(delq! "g" actions 'assoc)
(if doom-auto-discard
(cl-loop with doom-auto-accept = t
for (_key desc func) in actions
when desc
when (doom--straight-recommended-option-p prompt desc)
return (funcall func))
(print! (start "%s") (red prompt))
(print-group!
(terpri)
(let (options)
(print-group!
(print! " 1) Abort")
(cl-loop for (_key desc func) in actions
when desc
do (push func options)
and do
(print! "%2s) %s" (1+ (length options))
(if (doom--straight-recommended-option-p prompt desc)
(progn
(setq doom--straight-discard-options nil)
(green (concat desc " (Recommended)")))
desc))))
(terpri)
(let* ((options
(cons (lambda ()
(let ((doom-output-indent 0))
(terpri)
(print! (warn "Aborted")))
(kill-emacs 1))
(nreverse options)))
(prompt
(format! "How to proceed? (%s) "
(mapconcat #'number-to-string
(number-sequence 1 (length options))
", ")))
answer fn)
(while (null (nth (setq answer (1- (read-number prompt)))
options))
(print! (warn "%s is not a valid answer, try again.")
answer))
(funcall (nth answer options)))))))))
(defadvice! doom--straight-respect-print-indent-a (args)
"Indent straight progress messages to respect `doom-output-indent', so we
don't have to pass whitespace to `straight-use-package's fourth argument
everywhere we use it (and internally)."
:filter-args #'straight-use-package
(cl-destructuring-bind
(melpa-style-recipe &optional no-clone no-build cause interactive)
args
(list melpa-style-recipe no-clone no-build
(if (and (not cause)
(boundp 'doom-output-indent)
(> doom-output-indent 0))
(make-string (1- (or doom-output-indent 1)) 32)
cause)
interactive)))
;;
;;; CLI Commands
(load! "cli/help")
(load! "cli/install")
(defcligroup! "Maintenance"
"For managing your config and packages"
(defcli! (refresh re sync)
((if-necessary-p ["-n" "--if-necessary"] "Only regenerate autoloads files if necessary")
(inhibit-envvar-p ["-e"] "Don't regenerate the envvar file")
(prune-p ["-p" "--prune"] "Purge orphaned packages & regraft repos"))
"Ensure Doom is properly set up.
(defcli! (refresh re) ()
"Deprecated for 'doom sync'"
:hidden t
(user-error "'doom refresh' has been replaced with 'doom sync'. Use that instead"))
(defcli! (sync s)
((inhibit-envvar-p ["-e"] "Don't regenerate the envvar file")
(inhibit-elc-p ["-c"] "Don't recompile config")
(update-p ["-u"] "Update installed packages after syncing")
(prune-p ["-p" "--prune"] "Purge orphaned package repos & regraft them"))
"Synchronize your config with Doom Emacs.
This is the equivalent of running autoremove, install, autoloads, then
recompile. Run this whenever you:
@@ -238,53 +392,47 @@ recompile. Run this whenever you:
It will ensure that unneeded packages are removed, all needed packages are
installed, autoloads files are up-to-date and no byte-compiled files have gone
stale."
:bare t
(print! (start "Initiating a refresh of Doom Emacs..."))
(print-group!
(let (success)
(when (and (not inhibit-envvar-p)
(file-exists-p doom-env-file))
(doom-cli-reload-env-file 'force))
(print! (start "Synchronizing your config with Doom Emacs..."))
(print-group!
(delete-file doom-autoload-file)
(when (and (not inhibit-envvar-p)
(file-exists-p doom-env-file))
(doom-cli-reload-env-file 'force))
(run-hooks 'doom-sync-pre-hook)
(doom-cli-packages-install)
(doom-cli-packages-build)
(when update-p
(doom-cli-packages-update))
(doom-cli-packages-purge prune-p 'builds-p prune-p prune-p)
(run-hooks 'doom-sync-post-hook)
(when (doom-autoloads-reload)
(print! (info "Restart Emacs or use 'M-x doom/reload' for changes to take effect")))
t))
;; Ensures that no pre-existing state pollutes the generation of the new
;; autoloads files.
(mapc #'doom--cli-delete-autoloads-file
(list doom-autoload-file
doom-package-autoload-file))
(doom-initialize 'force 'noerror)
(doom-initialize-modules)
(doom-cli-reload-core-autoloads (not if-necessary-p))
(unwind-protect
(progn
(and (doom-cli-packages-install)
(setq success t))
(and (doom-cli-packages-build)
(setq success t))
(and (doom-cli-packages-purge prune-p 'builds-p prune-p prune-p)
(setq success t)))
(doom-cli-reload-package-autoloads (or success (not if-necessary-p)))
(doom-cli-byte-compile nil 'recompile))
t)))
(load! "cli/env")
(load! "cli/upgrade")
(load! "cli/packages")
(load! "cli/autoloads"))
(load! "cli/env")
(load! "cli/upgrade")
(load! "cli/packages")
(load! "cli/autoloads")
(defcligroup! "Diagnostics"
"For troubleshooting and diagnostics"
(load! "cli/doctor")
(load! "cli/debug")
(load! "cli/test"))
;; Our tests are broken at the moment. Working on fixing them, but for now we
;; disable them:
;; (load! "cli/test")
)
(defcligroup! "Compilation"
"For compiling Doom and your config"
(load! "cli/byte-compile"))
(defcligroup! "Utilities"
"Conveniences for interacting with Doom externally"
(defcli! run ()
(defcli! run (&rest args)
"Run Doom Emacs from bin/doom's parent directory.
All arguments are passed on to Emacs.
@@ -294,11 +442,9 @@ All arguments are passed on to Emacs.
WARNING: this command exists for convenience and testing. Doom will suffer
additional overhead by being started this way. For the best performance, it is
best to run Doom out of ~/.emacs.d and ~/.doom.d.")
;; (load! "cli/batch")
;; (load! "cli/org")
)
best to run Doom out of ~/.emacs.d and ~/.doom.d."
(apply #'doom-cli-execute-after invocation-name args)
nil))
(provide 'core-cli)
;;; core-cli.el ends here

View File

@@ -12,8 +12,8 @@ successfully sets indent_style/indent_size.")
(defvar-local doom-large-file-p nil)
(put 'doom-large-file-p 'permanent-local t)
(defvar doom-large-file-size-alist '((t . 1.0))
"An alist mapping major mode to filesize thresholds.
(defvar doom-large-file-size-alist '(("." . 1.0))
"An alist mapping regexps (like `auto-mode-alist') to filesize thresholds.
If a file is opened and discovered to be larger than the threshold, Doom
performs emergency optimizations to prevent Emacs from hanging, crashing or
@@ -42,15 +42,17 @@ possible."
(if (setq doom-large-file-p
(and buffer-file-name
(not doom-large-file-p)
(file-readable-p buffer-file-name)
(> (nth 7 (file-attributes buffer-file-name))
(* 1024 1024
(cdr (or (assq major-mode doom-large-file-size-alist)
(assq 't doom-large-file-size-alist)))))))
(file-exists-p buffer-file-name)
(ignore-errors
(> (nth 7 (file-attributes buffer-file-name))
(* 1024 1024
(assoc-default buffer-file-name doom-large-file-size-alist
#'string-match-p))))))
(prog1 (apply orig-fn args)
(if (memq major-mode doom-large-file-excluded-modes)
(setq doom-large-file-p nil)
(so-long-minor-mode +1)
(when (fboundp 'so-long-minor-mode) ; in case the user disabled it
(so-long-minor-mode +1))
(message "Large file detected! Cutting a few corners to improve performance...")))
(apply orig-fn args)))
@@ -77,68 +79,94 @@ possible."
t))))))
;; Don't autosave files or create lock/history/backup files. We don't want
;; copies of potentially sensitive material floating around, and we'll rely on
;; git and our own good fortune instead. Fingers crossed!
;; copies of potentially sensitive material floating around or polluting our
;; filesystem. We rely on git and our own good fortune instead. Fingers crossed!
(setq auto-save-default nil
create-lockfiles nil
make-backup-files nil
;; But have a place to store them in case we do use them...
auto-save-list-file-name (concat doom-cache-dir "autosave")
backup-directory-alist `(("." . ,(concat doom-cache-dir "backup/"))))
;; auto-save-list-file-name (concat doom-cache-dir "autosave")
auto-save-list-file-prefix (concat doom-cache-dir "autosave/")
auto-save-file-name-transforms `((".*" ,auto-save-list-file-prefix t))
backup-directory-alist `((".*" . ,(concat doom-cache-dir "backup/"))))
(after! tramp
(add-to-list 'backup-directory-alist (cons tramp-file-name-regexp nil)))
(add-hook! 'after-save-hook
(defun doom-guess-mode-h ()
"Guess mode when saving a file in `fundamental-mode'."
(and (eq major-mode 'fundamental-mode)
(buffer-file-name (buffer-base-buffer))
(eq (current-buffer) (window-buffer (selected-window))) ; only visible buffers
(set-auto-mode))))
(when (eq major-mode 'fundamental-mode)
(let ((buffer (or (buffer-base-buffer) (current-buffer))))
(and (buffer-file-name buffer)
(eq buffer (window-buffer (selected-window))) ; only visible buffers
(set-auto-mode))))))
;;
;;; Formatting
;; Indentation
(setq-default tab-width 4
tab-always-indent t
indent-tabs-mode nil
fill-column 80)
;; Favor spaces over tabs. Pls dun h8, but I think spaces (and 4 of them) is a
;; more consistent default than 8-space tabs. It can be changed on a per-mode
;; basis anyway (and is, where tabs are the canonical style, like go-mode).
(setq-default indent-tabs-mode nil
tab-width 4)
;; Word wrapping
(setq-default word-wrap t
truncate-lines t
truncate-partial-width-windows nil)
;; Make `tabify' and `untabify' only affect indentation. Not tabs/spaces in the
;; middle of a line.
(setq tabify-regexp "^\t* [ \t]+")
(setq sentence-end-double-space nil
delete-trailing-lines nil
require-final-newline t
tabify-regexp "^\t* [ \t]+") ; for :retab
;; An archaic default in the age of widescreen 4k displays? I disagree. We still
;; frequently split our terminals and editor frames, or have them side-by-side,
;; using up more of that newly available horizontal real-estate.
(setq-default fill-column 80)
;; Favor hard-wrapping in text modes
(add-hook 'text-mode-hook #'auto-fill-mode)
;; Continue wrapped words at whitespace, rather than in the middle of a word.
(setq-default word-wrap t)
;; ...but don't do any wrapping by default. It's expensive. Enable
;; `visual-line-mode' if you want soft line-wrapping. `auto-fill-mode' for hard
;; line-wrapping.
(setq-default truncate-lines t)
;; If enabled (and `truncate-lines' was disabled), soft wrapping no longer
;; occurs when that window is less than `truncate-partial-width-windows'
;; characters wide. We don't need this, and it's extra work for Emacs otherwise,
;; so off it goes.
(setq truncate-partial-width-windows nil)
;; This was a widespread practice in the days of typewriters. I actually prefer
;; it when writing prose with monospace fonts, but it is obsolete otherwise.
(setq sentence-end-double-space nil)
;; The POSIX standard defines a line is "a sequence of zero or more non-newline
;; characters followed by a terminating newline", so files should end in a
;; newline. Windows doesn't respect this (because it's Windows), but we should,
;; since programmers' tools tend to be POSIX compliant.
(setq require-final-newline t)
;; Default to soft line-wrapping in text modes. It is more sensibile for text
;; modes, even if hard wrapping is more performant.
(add-hook 'text-mode-hook #'visual-line-mode)
;;
;;; Clipboard / kill-ring
;; Eliminate duplicates in the kill ring. That is, if you kill the same thing
;; twice, you won't have to use M-y twice to get past it to older entries in the
;; kill ring.
;; Cull duplicates in the kill ring to reduce bloat and make the kill ring
;; easier to peruse (with `counsel-yank-pop' or `helm-show-kill-ring'.
(setq kill-do-not-save-duplicates t)
;;
;; Allow UTF or composed text from the clipboard, even in the terminal or on
;; non-X systems (like Windows or macOS), where only `STRING' is used.
(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
;; Save clipboard contents into kill-ring before replacing them
(setq save-interprogram-paste-before-kill t)
;; Fixes the clipboard in tty Emacs by piping clipboard I/O through xclip, xsel,
;; pb{copy,paste}, wl-copy, termux-clipboard-get, or getclip (cygwin).
(add-hook! 'tty-setup-hook
(defun doom-init-clipboard-in-tty-emacs-h ()
(and (not (getenv "SSH_CONNECTION"))
(require 'xclip nil t)
(xclip-mode +1))))
;; pb{copy,paste}, wl-copy, termux-clipboard-get, or getclip (cygwin); depending
;; on what is available.
(unless IS-WINDOWS
(add-hook! 'tty-setup-hook
(defun doom-init-clipboard-in-tty-emacs-h ()
(and (require 'clipetty nil t)
(global-clipetty-mode +1)))))
;;
@@ -146,6 +174,8 @@ possible."
(push '("/LICENSE\\'" . text-mode) auto-mode-alist)
(push '("\\.log\\'" . text-mode) auto-mode-alist)
(push '("rc\\'" . conf-mode) auto-mode-alist)
(push '("\\.\\(?:hex\\|nes\\)\\'" . hexl-mode) auto-mode-alist)
;;
@@ -164,13 +194,13 @@ possible."
;; Only prompts for confirmation when buffer is unsaved.
revert-without-query (list "."))
;; Instead of using `auto-revert-mode' or `global-auto-revert-mode', we employ
;; lazy auto reverting on `focus-in-hook' and `doom-switch-buffer-hook'.
;; Instead of `auto-revert-mode' or `global-auto-revert-mode', we lazily auto
;; revert; when we save a file or switch buffers/windows (or focus on Emacs).
;;
;; This is because autorevert abuses the heck out of inotify handles which can
;; grind Emacs to a halt if you do expensive IO (outside of Emacs) on the
;; files you have open (like compression). We only really need to revert
;; changes when we switch to a buffer or when we focus the Emacs frame.
;; Autorevert normally abuses the heck out of inotify handles which can grind
;; Emacs to a halt if you do expensive IO (outside of Emacs) on the files you
;; have open (like compression). The only alternative is aggressive polling,
;; which is unreliable and expensive with a lot of buffers open.
(defun doom-auto-revert-buffer-h ()
"Auto revert current buffer, if necessary."
(unless (or auto-revert-mode (active-minibuffer-window))
@@ -186,7 +216,7 @@ possible."
(use-package! recentf
;; Keep track of recently opened files
:defer-incrementally easymenu tree-widget timer
:after-call after-find-file
:hook (doom-first-file . recentf-mode)
:commands recentf-open-files
:config
(defun doom--recent-file-truename (file)
@@ -194,9 +224,11 @@ possible."
(not (file-remote-p file)))
(file-truename file)
file))
(setq recentf-filename-handlers '(doom--recent-file-truename abbreviate-file-name))
(setq recentf-save-file (concat doom-cache-dir "recentf")
(setq recentf-filename-handlers
'(substring-no-properties ; strip out lingering text properties
doom--recent-file-truename ; resolve symlinks of local files
abbreviate-file-name) ; replace $HOME with ~
recentf-save-file (concat doom-cache-dir "recentf")
recentf-auto-cleanup 'never
recentf-max-menu-items 0
recentf-max-saved-items 200)
@@ -214,22 +246,20 @@ possible."
"Add dired directory to recentf file list."
(recentf-add-file default-directory)))
(when doom-interactive-mode
(add-hook 'kill-emacs-hook #'recentf-cleanup)
(quiet! (recentf-mode +1))))
(add-hook 'kill-emacs-hook #'recentf-cleanup)
(advice-add #'recentf-load-list :around #'doom-shut-up-a))
(use-package! savehist
;; persist variables across sessions
:defer-incrementally custom
:after-call post-command-hook
:hook (doom-first-input . savehist-mode)
:init
(setq savehist-file (concat doom-cache-dir "savehist"))
:config
(setq savehist-file (concat doom-cache-dir "savehist")
savehist-save-minibuffer-history t
(setq savehist-save-minibuffer-history t
savehist-autosave-interval nil ; save on kill only
savehist-additional-variables '(kill-ring search-ring regexp-search-ring))
(savehist-mode +1)
(add-hook! 'kill-emacs-hook
(defun doom-unpropertize-kill-ring-h ()
"Remove text properties from `kill-ring' for a smaller savehist file."
@@ -241,31 +271,33 @@ possible."
(use-package! saveplace
;; persistent point location in buffers
:after-call after-find-file dired-initial-position-hook
:config
:hook (doom-first-file . save-place-mode)
:init
(setq save-place-file (concat doom-cache-dir "saveplace")
save-place-limit 100)
:config
(defadvice! doom--recenter-on-load-saveplace-a (&rest _)
"Recenter on cursor when loading a saved place."
:after-while #'save-place-find-file-hook
(if buffer-file-name (ignore-errors (recenter))))
(defadvice! doom--inhibit-saveplace-in-long-files-a (orig-fn &rest args)
:around #'save-place-to-alist
(unless doom-large-file-p
(apply orig-fn args)))
(defadvice! doom--dont-prettify-saveplace-cache-a (orig-fn)
"`save-place-alist-to-file' uses `pp' to prettify the contents of its cache.
`pp' can be expensive for longer lists, and there's no reason to prettify cache
files, so we replace calls to `pp' with the much faster `prin1'."
:around #'save-place-alist-to-file
(cl-letf (((symbol-function #'pp)
(symbol-function #'prin1)))
(funcall orig-fn)))
(save-place-mode +1))
(letf! ((#'pp #'prin1)) (funcall orig-fn))))
(use-package! server
:when (display-graphic-p)
:after-call pre-command-hook after-find-file focus-out-hook
:defer 1
:init
(when-let (name (getenv "EMACS_SERVER_NAME"))
(setq server-name name))
@@ -278,7 +310,8 @@ files, so we replace calls to `pp' with the much faster `prin1'."
;;; Packages
(use-package! better-jumper
:after-call pre-command-hook
:hook (doom-first-input . better-jumper-mode)
:hook (better-jumper-post-jump . recenter)
:preface
;; REVIEW Suppress byte-compiler warning spawning a *Compile-Log* buffer at
;; startup. This can be removed once gilbertw1/better-jumper#2 is merged.
@@ -288,17 +321,14 @@ files, so we replace calls to `pp' with the much faster `prin1'."
(global-set-key [remap evil-jump-backward] #'better-jumper-jump-backward)
(global-set-key [remap xref-pop-marker-stack] #'better-jumper-jump-backward)
:config
(better-jumper-mode +1)
(add-hook 'better-jumper-post-jump-hook #'recenter)
(defadvice! doom-set-jump-a (orig-fn &rest args)
(defun doom-set-jump-a (orig-fn &rest args)
"Set a jump point and ensure ORIG-FN doesn't set any new jump points."
(better-jumper-set-jump (if (markerp (car args)) (car args)))
(let ((evil--jumps-jumping t)
(better-jumper--jumping t))
(apply orig-fn args)))
(defadvice! doom-set-jump-maybe-a (orig-fn &rest args)
(defun doom-set-jump-maybe-a (orig-fn &rest args)
"Set a jump point if ORIG-FN returns non-nil."
(let ((origin (point-marker))
(result
@@ -329,20 +359,19 @@ files, so we replace calls to `pp' with the much faster `prin1'."
(use-package! dtrt-indent
;; Automatic detection of indent settings
:when doom-interactive-mode
:defer t
:init
(add-hook! '(change-major-mode-after-body-hook read-only-mode-hook)
(defun doom-detect-indentation-h ()
(unless (or (not after-init-time)
doom-inhibit-indent-detection
doom-large-file-p
(memq major-mode doom-detect-indentation-excluded-modes)
(member (substring (buffer-name) 0 1) '(" " "*")))
;; Don't display messages in the echo area, but still log them
(let ((inhibit-message (not doom-debug-mode)))
(dtrt-indent-mode +1)))))
:when doom-interactive-p
:hook ((change-major-mode-after-body read-only-mode) . doom-detect-indentation-h)
:config
(defun doom-detect-indentation-h ()
(unless (or (not after-init-time)
doom-inhibit-indent-detection
doom-large-file-p
(memq major-mode doom-detect-indentation-excluded-modes)
(member (substring (buffer-name) 0 1) '(" " "*")))
;; Don't display messages in the echo area, but still log them
(let ((inhibit-message (not doom-debug-p)))
(dtrt-indent-mode +1))))
;; Enable dtrt-indent even in smie modes so that it can update `tab-width',
;; `standard-indent' and `evil-shift-width' there as well.
(setq dtrt-indent-run-after-smie t)
@@ -358,18 +387,14 @@ files, so we replace calls to `pp' with the much faster `prin1'."
`nim-mode'. This prevents them from leaving Emacs in a broken state."
:around #'dtrt-indent-mode
(let ((dtrt-indent-run-after-smie dtrt-indent-run-after-smie))
(cl-letf* ((old-smie-config-guess (symbol-function 'smie-config-guess))
(old-smie-config--guess (symbol-function 'symbol-config--guess))
((symbol-function 'symbol-config--guess)
(lambda (beg end)
(funcall old-smie-config--guess beg (min end 10000))))
((symbol-function 'smie-config-guess)
(lambda ()
(condition-case e (funcall old-smie-config-guess)
(error (setq dtrt-indent-run-after-smie t)
(message "[WARNING] Indent detection: %s"
(error-message-string e))
(message "")))))) ; warn silently
(letf! ((defun symbol-config--guess (beg end)
(funcall symbol-config--guess beg (min end 10000)))
(defun smie-config-guess ()
(condition-case e (funcall smie-config-guess)
(error (setq dtrt-indent-run-after-smie t)
(message "[WARNING] Indent detection: %s"
(error-message-string e))
(message ""))))) ; warn silently
(funcall orig-fn arg)))))
@@ -377,17 +402,16 @@ files, so we replace calls to `pp' with the much faster `prin1'."
;; a better *help* buffer
:commands helpful--read-symbol
:init
(define-key!
[remap describe-function] #'helpful-callable
[remap describe-command] #'helpful-command
[remap describe-variable] #'helpful-variable
[remap describe-key] #'helpful-key
[remap describe-symbol] #'doom/describe-symbol)
(global-set-key [remap describe-function] #'helpful-callable)
(global-set-key [remap describe-command] #'helpful-command)
(global-set-key [remap describe-variable] #'helpful-variable)
(global-set-key [remap describe-key] #'helpful-key)
(global-set-key [remap describe-symbol] #'helpful-symbol)
(defun doom-use-helpful-a (orig-fn &rest args)
"Force ORIG-FN to use helpful instead of the old describe-* commands."
(cl-letf (((symbol-function #'describe-function) #'helpful-function)
((symbol-function #'describe-variable) #'helpful-variable))
(letf! ((#'describe-function #'helpful-function)
(#'describe-variable #'helpful-variable))
(apply orig-fn args)))
(after! apropos
@@ -411,30 +435,33 @@ files, so we replace calls to `pp' with the much faster `prin1'."
(use-package! smartparens
;; Auto-close delimiters and blocks as you type. It's more powerful than that,
;; but that is all Doom uses it for.
:after-call doom-switch-buffer-hook after-find-file
:hook (doom-first-buffer . smartparens-global-mode)
:commands sp-pair sp-local-pair sp-with-modes sp-point-in-comment sp-point-in-string
:config
;; smartparens recognizes `slime-mrepl-mode', but not `sly-mrepl-mode', so...
(add-to-list 'sp-lisp-modes 'sly-mrepl-mode)
;; Load default smartparens rules for various languages
(require 'smartparens-config)
;; Overlays are too distracting and not terribly helpful. show-parens does
;; this for us already, so...
;; this for us already (and is faster), so...
(setq sp-highlight-pair-overlay nil
sp-highlight-wrap-overlay nil
sp-highlight-wrap-tag-overlay nil)
;; But if someone does want overlays enabled, evil users will be stricken with
;; an off-by-one issue where smartparens assumes you're outside the pair when
;; you're really at the last character in insert mode. We must correct this
;; vile injustice.
(setq sp-show-pair-from-inside t)
;; ...and stay highlighted until we've truly escaped the pair!
(setq sp-cancel-autoskip-on-backward-movement nil)
(with-eval-after-load 'evil
;; But if someone does want overlays enabled, evil users will be stricken
;; with an off-by-one issue where smartparens assumes you're outside the
;; pair when you're really at the last character in insert mode. We must
;; correct this vile injustice.
(setq sp-show-pair-from-inside t)
;; ...and stay highlighted until we've truly escaped the pair!
(setq sp-cancel-autoskip-on-backward-movement nil))
;; The default is 100, because smartparen's scans are relatively expensive
;; (especially with large pair lists for somoe modes), we halve it, as a
;; (especially with large pair lists for some modes), we reduce it, as a
;; better compromise between performance and accuracy.
(setq sp-max-prefix-length 50)
;; This speeds up smartparens. No pair has any business being longer than 4
;; characters; if they must, the modes that need it set it buffer-locally.
(setq sp-max-prefix-length 25)
;; No pair has any business being longer than 4 characters; if they must, set
;; it buffer-locally. It's less work for smartparens.
(setq sp-max-pair-length 4)
;; This isn't always smart enough to determine when we're in a string or not.
;; See https://github.com/Fuco1/smartparens/issues/783.
@@ -442,14 +469,15 @@ files, so we replace calls to `pp' with the much faster `prin1'."
;; Silence some harmless but annoying echo-area spam
(dolist (key '(:unmatched-expression :no-matching-tag))
(setf (cdr (assq key sp-message-alist)) nil))
(setf (alist-get key sp-message-alist) nil))
(add-hook! 'minibuffer-setup-hook
(defun doom-init-smartparens-in-minibuffer-maybe-h ()
"Enable `smartparens-mode' in the minibuffer, during `eval-expression' or
`evil-ex'."
(when (memq this-command '(eval-expression evil-ex))
(smartparens-mode))))
"Enable `smartparens-mode' in the minibuffer, during `eval-expression',
`pp-eval-expression' or `evil-ex'."
(and (memq this-command '(eval-expression pp-eval-expression evil-ex))
smartparens-global-mode
(smartparens-mode))))
;; You're likely writing lisp in the minibuffer, therefore, disable these
;; quote pairs, which lisps doesn't use for strings:
@@ -467,16 +495,13 @@ files, so we replace calls to `pp' with the much faster `prin1'."
(defun doom-disable-smartparens-mode-maybe-h ()
(when smartparens-mode
(setq-local doom-buffer-smartparens-mode t)
(turn-off-smartparens-mode))))
(smartparens-global-mode +1))
(turn-off-smartparens-mode)))))
(use-package! so-long
:after-call after-find-file
:hook (doom-first-file . global-so-long-mode)
:config
(when doom-interactive-mode
(global-so-long-mode +1))
(setq so-long-threshold 400) ; reduce false positives w/ larger threshold
;; Don't disable syntax highlighting and line numbers, or make the buffer
;; read-only, in `so-long-minor-mode', so we can have a basic editing
;; experience in them, at least. It will remain off in `so-long-mode',
@@ -486,6 +511,8 @@ files, so we replace calls to `pp' with the much faster `prin1'."
(delq! 'buffer-read-only so-long-variable-overrides 'assq)
;; ...but at least reduce the level of syntax highlighting
(add-to-list 'so-long-variable-overrides '(font-lock-maximum-decoration . 1))
;; ...and insist that save-place not operate in large/long files
(add-to-list 'so-long-variable-overrides '(save-place-alist . nil))
;; Text files could possibly be too long too
(add-to-list 'so-long-target-modes 'text-mode)
;; But disable everything else that may be unnecessary/expensive for large
@@ -493,6 +520,7 @@ files, so we replace calls to `pp' with the much faster `prin1'."
(appendq! so-long-minor-modes
'(flycheck-mode
flyspell-mode
spell-fu-mode
eldoc-mode
smartparens-mode
highlight-numbers-mode
@@ -502,60 +530,28 @@ files, so we replace calls to `pp' with the much faster `prin1'."
undo-tree-mode
highlight-indent-guides-mode
hl-fill-column-mode))
;; HACK Fix #2183: `so-long-detected-long-line-p' tries to parse comment
;; syntax, but in some buffers comment state isn't initialized, leading
;; to a wrong-type-argument: stringp error.
(defun doom-buffer-has-long-lines-p ()
(when (bound-and-true-p comment-use-syntax)
;; HACK Fix #2183: `so-long-detected-long-line-p' tries to parse comment
;; syntax, but in some buffers comment state isn't initialized,
;; leading to a wrong-type-argument: stringp error.
(let ((so-long-skip-leading-comments (bound-and-true-p comment-use-syntax))
;; HACK If visual-line-mode is on, then false positives are more
;; likely, so up the threshold. More so in text-mode, since long
;; paragraphs are the norm.
(so-long-threshold
(if visual-line-mode
(* so-long-threshold
(if (derived-mode-p 'text-mode)
3
2))
so-long-threshold)))
(so-long-detected-long-line-p)))
(setq so-long-predicate #'doom-buffer-has-long-lines-p))
(use-package! undo-tree
;; Branching & persistent undo
:after-call doom-switch-buffer-hook after-find-file
:config
(setq undo-tree-auto-save-history t
;; Increase undo-limits by a factor of ten to avoid emacs prematurely
;; truncating the undo history and corrupting the tree. See
;; https://github.com/syl20bnr/spacemacs/issues/12110
undo-limit 800000
undo-strong-limit 12000000
undo-outer-limit 120000000
undo-tree-history-directory-alist
`(("." . ,(concat doom-cache-dir "undo-tree-hist/"))))
;; Compress undo-tree history files with zstd, if available. File size isn't
;; the (only) concern here: the file IO barrier is slow for Emacs to cross;
;; reading a tiny file and piping it in-memory through zstd is *slightly*
;; faster than Emacs reading the entire undo-tree file from the get go (on
;; SSDs). Whether or not that's true in practice, we still enjoy zstd's ~80%
;; file savings (these files add up over time and zstd is so incredibly fast).
(when (executable-find "zstd")
(defadvice! doom--undo-tree-make-history-save-file-name-a (file)
:filter-return #'undo-tree-make-history-save-file-name
(concat file ".zst")))
;; Strip text properties from undo-tree data to stave off bloat. File size
;; isn't the concern here; undo cache files bloat easily, which can cause
;; freezing, crashes, GC-induced stuttering or delays when opening files.
(defadvice! doom--undo-tree-strip-text-properties-a (&rest _)
:before #'undo-list-transfer-to-tree
(dolist (item buffer-undo-list)
(and (consp item)
(stringp (car item))
(setcar item (substring-no-properties (car item))))))
(global-undo-tree-mode +1))
(use-package! ws-butler
;; a less intrusive `delete-trailing-whitespaces' on save
:after-call after-find-file
:config
(appendq! ws-butler-global-exempt-modes
'(special-mode comint-mode term-mode eshell-mode))
(ws-butler-global-mode))
:hook (doom-first-buffer . ws-butler-global-mode))
(provide 'core-editor)
;;; core-editor.el ends here

View File

@@ -26,9 +26,12 @@ and Emacs states, and for non-evil users.")
;;
;;; Keybind settings
(when IS-MAC
(setq mac-command-modifier 'super
mac-option-modifier 'meta))
(cond (IS-MAC
(setq mac-command-modifier 'super
mac-option-modifier 'meta))
(IS-WINDOWS
(setq w32-lwindow-modifier 'super
w32-rwindow-modifier 'super)))
;;
@@ -77,7 +80,8 @@ all hooks after it are ignored.")
:init
;; Convenience aliases
(defalias 'define-key! #'general-def)
(defalias 'unmap! #'general-unbind))
(defalias 'undefine-key! #'general-unbind))
;; HACK `map!' uses this instead of `define-leader-key!' because it consumes
;; 20-30% more startup time, so we reimplement it ourselves.
@@ -108,8 +112,8 @@ all hooks after it are ignored.")
(general--concat t doom-leader-key ,key)
,desc))))))))
(macroexp-progn
(cons `(after! which-key ,@(nreverse wkforms))
(nreverse forms)))))
(append (and wkforms `((after! which-key ,@(nreverse wkforms))))
(nreverse forms)))))
(defmacro define-leader-key! (&rest args)
"Define <leader> keys.
@@ -175,8 +179,7 @@ localleader prefix."
;;; Packages
(use-package! which-key
:defer 1
:after-call pre-command-hook
:hook (doom-first-input . which-key-mode)
:init
(setq which-key-sort-order #'which-key-prefix-then-key-order
which-key-sort-uppercase-first nil
@@ -191,9 +194,7 @@ localleader prefix."
(setq-hook! 'which-key-init-buffer-hook line-spacing 3)
(which-key-add-key-based-replacements doom-leader-key "<leader>")
(which-key-add-key-based-replacements doom-localleader-key "<localleader>")
(which-key-mode +1))
(which-key-add-key-based-replacements doom-localleader-key "<localleader>"))
;;
@@ -215,21 +216,11 @@ localleader prefix."
For example, :nvi will map to (list 'normal 'visual 'insert). See
`doom-evil-state-alist' to customize this."
(cl-loop for l across (substring (symbol-name keyword) 1)
if (cdr (assq l doom-evil-state-alist)) collect it
(cl-loop for l across (doom-keyword-name keyword)
if (assq l doom-evil-state-alist) collect (cdr it)
else do (error "not a valid state: %s" l)))
;; Register keywords for proper indentation (see `map!')
(put :after 'lisp-indent-function 'defun)
(put :desc 'lisp-indent-function 'defun)
(put :leader 'lisp-indent-function 'defun)
(put :localleader 'lisp-indent-function 'defun)
(put :map 'lisp-indent-function 'defun)
(put :mode 'lisp-indent-function 'defun)
(put :prefix 'lisp-indent-function 'defun)
(put :prefix-map 'lisp-indent-function 'defun)
;; specials
(defvar doom--map-forms nil)
(defvar doom--map-fn nil)

View File

@@ -1,8 +1,5 @@
;;; core-lib.el -*- lexical-binding: t; -*-
(require 'cl-lib)
(require 'subr-x)
;;
;;; Helpers
@@ -69,10 +66,10 @@ list is returned as-is."
(substring (symbol-name keyword) 1))
(defmacro doom-log (format-string &rest args)
"Log to *Messages* if `doom-debug-mode' is on.
"Log to *Messages* if `doom-debug-p' is on.
Does not interrupt the minibuffer if it is in use, but still logs to *Messages*.
Accepts the same arguments as `message'."
`(when doom-debug-mode
`(when doom-debug-p
(let ((inhibit-message (active-minibuffer-window)))
(message
,(concat (propertize "DOOM " 'face 'font-lock-comment-face)
@@ -85,6 +82,53 @@ Accepts the same arguments as `message'."
format-string)
,@args))))
(defun doom-try-run-hook (hook)
"Run HOOK (a hook function) with better error handling.
Meant to be used with `run-hook-wrapped'."
(doom-log "Running doom hook: %s" hook)
(condition-case e
(funcall hook)
((debug error)
(signal 'doom-hook-error (list hook e))))
;; return nil so `run-hook-wrapped' won't short circuit
nil)
(defun doom-load-envvars-file (file &optional noerror)
"Read and set envvars from FILE.
If NOERROR is non-nil, don't throw an error if the file doesn't exist or is
unreadable. Returns the names of envvars that were changed."
(if (null (file-exists-p file))
(unless noerror
(signal 'file-error (list "No envvar file exists" file)))
(when-let
(env
(with-temp-buffer
(save-excursion
(setq-local coding-system-for-read 'utf-8)
(insert "\0\n") ; to prevent off-by-one
(insert-file-contents file))
(save-match-data
(when (re-search-forward "\0\n *\\([^#= \n]*\\)=" nil t)
(setq
env (split-string (buffer-substring (match-beginning 1) (point-max))
"\0\n"
'omit-nulls))))))
(setq-default
process-environment
(append (nreverse env)
(default-value 'process-environment))
exec-path
(append (split-string (getenv "PATH") path-separator t)
(list exec-directory))
shell-file-name
(or (getenv "SHELL")
(default-value 'shell-file-name)))
env)))
;;
;;; Functional library
(defalias 'doom-partial #'apply-partially)
(defun doom-rpartial (fn &rest args)
@@ -93,6 +137,7 @@ Accepts the same arguments as `message'."
ARGS is a list of the last N arguments to pass to FUN. The result is a new
function which does the same as FUN, except that the last N arguments are fixed
at the values with which this function was called."
(declare (side-effect-free t))
(lambda (&rest pre-args)
(apply fn (append pre-args args))))
@@ -100,23 +145,10 @@ at the values with which this function was called."
;;
;;; Sugars
(defmacro λ! (&rest body)
"Expands to (lambda () (interactive) ,@body).
A factory for quickly producing interaction commands, particularly for keybinds
or aliases."
(declare (doc-string 1))
`(lambda () (interactive) ,@body))
(defalias 'lambda! 'λ!)
(defun λ!! (command &optional arg)
"Expands to a command that interactively calls COMMAND with prefix ARG.
A factory for quickly producing interactive, prefixed commands for keybinds or
aliases."
(declare (doc-string 1))
(lambda () (interactive)
(let ((current-prefix-arg arg))
(call-interactively command))))
(defalias 'lambda!! 'λ!!)
(defun dir! ()
"Returns the directory of the emacs lisp file this macro is called from."
(when-let (path (file!))
(directory-file-name (file-name-directory path))))
(defun file! ()
"Return the emacs lisp file this macro is called from."
@@ -127,18 +159,152 @@ aliases."
(buffer-file-name)
((error "Cannot get this file-path"))))
(defun dir! ()
"Returns the directory of the emacs lisp file this macro is called from."
(when-let (path (file!))
(directory-file-name (file-name-directory path))))
(defmacro letenv! (envvars &rest body)
"Lexically bind ENVVARS in BODY, like `let' but for `process-environment'."
(declare (indent 1))
`(let ((process-environment (copy-sequence process-environment)))
(dolist (var (list ,@(cl-loop for (var val) in envvars
collect `(cons ,var ,val))))
(setenv (car var) (cdr var)))
,@body))
(defmacro letf! (bindings &rest body)
"Temporarily rebind function and macros in BODY.
BINDINGS is either a) a list of, or a single, `defun' or `defmacro'-ish form, or
b) a list of (PLACE VALUE) bindings as `cl-letf*' would accept.
TYPE is either `defun' or `defmacro'. NAME is the name of the function. If an
original definition for NAME exists, it can be accessed as a lexical variable by
the same name, for use with `funcall' or `apply'. ARGLIST and BODY are as in
`defun'.
\(fn ((TYPE NAME ARGLIST &rest BODY) ...) BODY...)"
(declare (indent defun))
(setq body (macroexp-progn body))
(when (memq (car bindings) '(defun defmacro))
(setq bindings (list bindings)))
(dolist (binding (reverse bindings) (macroexpand body))
(let ((type (car binding))
(rest (cdr binding)))
(setq
body (pcase type
(`defmacro `(cl-macrolet ((,@rest)) ,body))
(`defun `(cl-letf* ((,(car rest) (symbol-function #',(car rest)))
((symbol-function #',(car rest))
(lambda ,(cadr rest) ,@(cddr rest))))
(ignore ,(car rest))
,body))
(_
(when (eq (car-safe type) 'function)
(setq type (list 'symbol-function type)))
(list 'cl-letf (list (cons type rest)) body)))))))
(defmacro quiet! (&rest forms)
"Run FORMS without generating any output.
This silences calls to `message', `load', `write-region' and anything that
writes to `standard-output'."
`(if doom-debug-p
(progn ,@forms)
,(if doom-interactive-p
`(let ((inhibit-message t)
(save-silently t))
(prog1 ,@forms (message "")))
`(letf! ((standard-output (lambda (&rest _)))
(defun message (&rest _))
(defun load (file &optional noerror nomessage nosuffix must-suffix)
(funcall load file noerror t nosuffix must-suffix))
(defun write-region (start end filename &optional append visit lockname mustbenew)
(unless visit (setq visit 'no-message))
(funcall write-region start end filename append visit lockname mustbenew)))
,@forms))))
(defmacro if! (cond then &rest body)
"Expands to THEN if COND is non-nil, to BODY otherwise.
COND is checked at compile/expansion time, allowing BODY to be omitted
entirely when the elisp is byte-compiled. Use this for forms that contain
expensive macros that could safely be removed at compile time."
(declare (indent 2))
(if (eval cond)
then
(macroexp-progn body)))
(defmacro when! (cond &rest body)
"Expands to BODY if CONDITION is non-nil at compile/expansion time.
See `if!' for details on this macro's purpose."
(declare (indent 1))
(when (eval cond)
(macroexp-progn body)))
;;; Closure factories
(defmacro fn! (arglist &rest body)
"Expands to (cl-function (lambda ARGLIST BODY...))"
(declare (indent defun) (doc-string 1) (pure t) (side-effect-free t))
`(cl-function (lambda ,arglist ,@body)))
(defmacro cmd! (&rest body)
"Expands to (lambda () (interactive) ,@body).
A factory for quickly producing interaction commands, particularly for keybinds
or aliases."
(declare (doc-string 1) (pure t) (side-effect-free t))
`(lambda (&rest _) (interactive) ,@body))
(defmacro cmd!! (command &rest args)
"Expands to a closure that interactively calls COMMAND with ARGS.
A factory for quickly producing interactive, prefixed commands for keybinds or
aliases."
(declare (doc-string 1) (pure t) (side-effect-free t))
`(lambda (&rest _) (interactive)
(funcall-interactively ,command ,@args)))
(defmacro cmds! (&rest branches)
"Expands to a `menu-item' dispatcher for keybinds."
(declare (doc-string 1))
(let ((docstring (if (stringp (car branches)) (pop branches) ""))
fallback)
(when (cl-oddp (length branches))
(setq fallback (car (last branches))
branches (butlast branches)))
`(general-predicate-dispatch ,fallback
:docstring ,docstring
,@branches)))
;; For backwards compatibility
(defalias 'λ! 'cmd!)
(defalias 'λ!! 'cmd!!)
;; DEPRECATED These have been superseded by `cmd!' and `cmd!!'
(define-obsolete-function-alias 'lambda! 'cmd! "3.0.0")
(define-obsolete-function-alias 'lambda!! 'cmd!! "3.0.0")
;;; Mutation
(defmacro appendq! (sym &rest lists)
"Append LISTS to SYM in place."
`(setq ,sym (append ,sym ,@lists)))
(defmacro setq! (&rest settings)
"A stripped-down `customize-set-variable' with the syntax of `setq'."
"A stripped-down `customize-set-variable' with the syntax of `setq'.
This can be used as a drop-in replacement for `setq'. Particularly when you know
a variable has a custom setter (a :set property in its `defcustom' declaration).
This triggers setters. `setq' does not."
(macroexp-progn
(cl-loop for (var val) on settings by 'cddr
collect `(funcall (or (get ',var 'custom-set) #'set)
',var ,val))))
(defmacro delq! (elt list &optional fetcher)
"`delq' ELT from LIST in-place.
If FETCHER is a function, ELT is used as the key in LIST (an alist)."
`(setq ,list
(delq ,(if fetcher
`(funcall ,fetcher ,elt ,list)
elt)
,list)))
(defmacro pushnew! (place &rest values)
"Push VALUES sequentially into PLACE, if they aren't already present.
This is a variadic `cl-pushnew'."
@@ -150,31 +316,8 @@ This is a variadic `cl-pushnew'."
"Prepend LISTS to SYM in place."
`(setq ,sym (append ,@lists ,sym)))
(defmacro appendq! (sym &rest lists)
"Append LISTS to SYM in place."
`(setq ,sym (append ,sym ,@lists)))
(defmacro nconcq! (sym &rest lists)
"Append LISTS to SYM by altering them in place."
`(setq ,sym (nconc ,sym ,@lists)))
(defmacro delq! (elt list &optional fetcher)
"`delq' ELT from LIST in-place.
If FETCHER is a function, ELT is used as the key in LIST (an alist)."
`(setq ,list
(delq ,(if fetcher
`(funcall ,fetcher ,elt ,list)
elt)
,list)))
(defmacro letenv! (envvars &rest body)
"Lexically bind ENVVARS in BODY, like `let' but for `process-environment'."
`(let ((process-environment (copy-sequence process-environment)))
(dolist (var ',envvars)
(setenv (car var) (cadr var)))
,@body))
;;; Loading
(defmacro add-load-path! (&rest dirs)
"Add DIRS to `load-path', relative to the current file.
The current file is the file from which `add-to-load-path!' is used."
@@ -183,6 +326,136 @@ The current file is the file from which `add-to-load-path!' is used."
(dolist (dir (list ,@dirs))
(cl-pushnew (expand-file-name dir) load-path))))
(defmacro after! (package &rest body)
"Evaluate BODY after PACKAGE have loaded.
PACKAGE is a symbol or list of them. These are package names, not modes,
functions or variables. It can be:
- An unquoted package symbol (the name of a package)
(after! helm BODY...)
- An unquoted list of package symbols (i.e. BODY is evaluated once both magit
and git-gutter have loaded)
(after! (magit git-gutter) BODY...)
- An unquoted, nested list of compound package lists, using any combination of
:or/:any and :and/:all
(after! (:or package-a package-b ...) BODY...)
(after! (:and package-a package-b ...) BODY...)
(after! (:and package-a (:or package-b package-c) ...) BODY...)
Without :or/:any/:and/:all, :and/:all are implied.
This is a wrapper around `eval-after-load' that:
1. Suppresses warnings for disabled packages at compile-time
2. No-ops for package that are disabled by the user (via `package!')
3. Supports compound package statements (see below)
4. Prevents eager expansion pulling in autoloaded macros all at once"
(declare (indent defun) (debug t))
(if (symbolp package)
(unless (memq package (bound-and-true-p doom-disabled-packages))
(list (if (or (not (bound-and-true-p byte-compile-current-file))
(require package nil 'noerror))
#'progn
#'with-no-warnings)
(let ((body (macroexp-progn body)))
`(if (featurep ',package)
,body
;; We intentionally avoid `with-eval-after-load' to prevent
;; eager macro expansion from pulling (or failing to pull) in
;; autoloaded macros/packages.
(eval-after-load ',package ',body)))))
(let ((p (car package)))
(cond ((not (keywordp p))
`(after! (:and ,@package) ,@body))
((memq p '(:or :any))
(macroexp-progn
(cl-loop for next in (cdr package)
collect `(after! ,next ,@body))))
((memq p '(:and :all))
(dolist (next (cdr package))
(setq body `((after! ,next ,@body))))
(car body))))))
(defun doom--handle-load-error (e target path)
(let* ((source (file-name-sans-extension target))
(err (cond ((not (featurep 'core))
(cons 'error (file-name-directory path)))
((file-in-directory-p source doom-core-dir)
(cons 'doom-error doom-core-dir))
((file-in-directory-p source doom-private-dir)
(cons 'doom-private-error doom-private-dir))
((cons 'doom-module-error doom-emacs-dir)))))
(signal (car err)
(list (file-relative-name
(concat source ".el")
(cdr err))
e))))
(defmacro load! (filename &optional path noerror)
"Load a file relative to the current executing file (`load-file-name').
FILENAME is either a file path string or a form that should evaluate to such a
string at run time. PATH is where to look for the file (a string representing a
directory path). If omitted, the lookup is relative to either `load-file-name',
`byte-compile-current-file' or `buffer-file-name' (checked in that order).
If NOERROR is non-nil, don't throw an error if the file doesn't exist."
(let* ((path (or path
(dir!)
(error "Could not detect path to look for '%s' in"
filename)))
(file (if path
`(expand-file-name ,filename ,path)
filename)))
`(condition-case-unless-debug e
(let (file-name-handler-alist)
(load ,file ,noerror 'nomessage))
(doom-error (signal (car e) (cdr e)))
(error (doom--handle-load-error e ,file ,path)))))
(defmacro defer-until! (condition &rest body)
"Run BODY when CONDITION is true (checks on `after-load-functions'). Meant to
serve as a predicated alternative to `after!'."
(declare (indent defun) (debug t))
`(if ,condition
(progn ,@body)
,(let ((fn (intern (format "doom--delay-form-%s-h" (sxhash (cons condition body))))))
`(progn
(fset ',fn (lambda (&rest args)
(when ,(or condition t)
(remove-hook 'after-load-functions #',fn)
(unintern ',fn nil)
(ignore args)
,@body)))
(put ',fn 'permanent-local-hook t)
(add-hook 'after-load-functions #',fn)))))
(defmacro defer-feature! (feature &rest fns)
"Pretend FEATURE hasn't been loaded yet, until FEATURE-hook or FN runs.
Some packages (like `elisp-mode' and `lisp-mode') are loaded immediately at
startup, which will prematurely trigger `after!' (and `with-eval-after-load')
blocks. To get around this we make Emacs believe FEATURE hasn't been loaded yet,
then wait until FEATURE-hook (or MODE-hook, if FN is provided) is triggered to
reverse this and trigger `after!' blocks at a more reasonable time."
(let ((advice-fn (intern (format "doom--defer-feature-%s-a" feature))))
`(progn
(delq! ',feature features)
(defadvice! ,advice-fn (&rest _)
:before ',fns
;; Some plugins (like yasnippet) will invoke a fn early to parse
;; code, which would prematurely trigger this. In those cases, well
;; behaved plugins will use `delay-mode-hooks', which we can check for:
(unless delay-mode-hooks
;; ...Otherwise, announce to the world this package has been loaded,
;; so `after!' handlers can react.
(provide ',feature)
(dolist (fn ',fns)
(advice-remove fn #',advice-fn)))))))
;;; Hooks
(defvar doom--transient-counter 0)
(defmacro add-transient-hook! (hook-or-function &rest forms)
"Attaches a self-removing function to HOOK-OR-FUNCTION.
@@ -193,9 +466,12 @@ HOOK-OR-FUNCTION can be a quoted hook or a sharp-quoted function (which will be
advised)."
(declare (indent 1))
(let ((append (if (eq (car forms) :after) (pop forms)))
(fn (intern (format "doom--transient-%s-h" (sxhash hook-or-function)))))
;; Avoid `make-symbol' and `gensym' here because an interned symbol is
;; easier to debug in backtraces (and is visible to `describe-function')
(fn (intern (format "doom--transient-%d-h" (cl-incf doom--transient-counter)))))
`(let ((sym ,hook-or-function))
(defun ,fn (&rest _)
,(format "Transient hook for %S" (doom-unquote hook-or-function))
,@forms
(let ((sym ,hook-or-function))
(cond ((functionp sym) (advice-remove sym #',fn))
@@ -207,20 +483,29 @@ advised)."
(put ',fn 'permanent-local-hook t)
(add-hook sym #',fn ,append))))))
(defmacro add-hook-trigger! (hook-var &rest targets)
"TODO"
`(let ((fn (intern (format "%s-h" ,hook-var))))
(fset fn (lambda (&rest _) (run-hooks ,hook-var) (set ,hook-var nil)))
(put ,hook-var 'permanent-local t)
(dolist (on (list ,@targets))
(if (functionp on)
(advice-add on :before fn)
(add-hook on fn)))))
(defmacro add-hook! (hooks &rest rest)
"A convenience macro for adding N functions to M hooks.
If N and M = 1, there's no benefit to using this macro over `add-hook'.
This macro accepts, in order:
1. Optional properties :local and/or :append, which will make the hook
1. The mode(s) or hook(s) to add to. This is either an unquoted mode, an
unquoted list of modes, a quoted hook variable or a quoted list of hook
variables.
2. Optional properties :local and/or :append, which will make the hook
buffer-local or append to the list of hooks (respectively),
2. The hook(s) to be added to: either an unquoted mode, an unquoted list of
modes, a quoted hook variable or a quoted list of hook variables. If
unquoted, '-hook' will be appended to each symbol.
3. The function(s) to be added: this can be one function, a list thereof, a
list of `defun's, or body forms (implicitly wrapped in a closure).
3. The function(s) to be added: this can be one function, a quoted list
thereof, a list of `defun's, or body forms (implicitly wrapped in a
lambda).
\(fn HOOKS [:append :local] FUNCTIONS)"
(declare (indent (lambda (indent-point state)
@@ -301,106 +586,8 @@ If N and M = 1, there's no benefit to using this macro over `remove-hook'.
in (doom--setq-hook-fns hooks vars 'singles)
collect `(remove-hook ',hook #',fn))))
(defmacro load! (filename &optional path noerror)
"Load a file relative to the current executing file (`load-file-name').
FILENAME is either a file path string or a form that should evaluate to such a
string at run time. PATH is where to look for the file (a string representing a
directory path). If omitted, the lookup is relative to either `load-file-name',
`byte-compile-current-file' or `buffer-file-name' (checked in that order).
If NOERROR is non-nil, don't throw an error if the file doesn't exist."
(let* ((path (or path
(dir!)
(error "Could not detect path to look for '%s' in"
filename)))
(file (if path
`(expand-file-name ,filename ,path)
filename)))
`(condition-case-unless-debug e
(let (file-name-handler-alist)
(load ,file ,noerror 'nomessage))
(doom-error (signal (car e) (cdr e)))
(error
(let* ((source (file-name-sans-extension ,file))
(err (cond ((not (featurep 'core))
(cons 'error (file-name-directory path)))
((file-in-directory-p source doom-core-dir)
(cons 'doom-error doom-core-dir))
((file-in-directory-p source doom-private-dir)
(cons 'doom-private-error doom-private-dir))
((cons 'doom-module-error doom-emacs-dir)))))
(signal (car err)
(list (file-relative-name
(concat source ".el")
(cdr err))
e)))))))
(defmacro defer-until! (condition &rest body)
"Run BODY when CONDITION is true (checks on `after-load-functions'). Meant to
serve as a predicated alternative to `after!'."
(declare (indent defun) (debug t))
`(if ,condition
(progn ,@body)
,(let ((fn (intern (format "doom--delay-form-%s-h" (sxhash (cons condition body))))))
`(progn
(fset ',fn (lambda (&rest args)
(when ,(or condition t)
(remove-hook 'after-load-functions #',fn)
(unintern ',fn nil)
(ignore args)
,@body)))
(put ',fn 'permanent-local-hook t)
(add-hook 'after-load-functions #',fn)))))
(defmacro defer-feature! (feature &optional fn)
"Pretend FEATURE hasn't been loaded yet, until FEATURE-hook or FN runs.
Some packages (like `elisp-mode' and `lisp-mode') are loaded immediately at
startup, which will prematurely trigger `after!' (and `with-eval-after-load')
blocks. To get around this we make Emacs believe FEATURE hasn't been loaded yet,
then wait until FEATURE-hook (or MODE-hook, if FN is provided) is triggered to
reverse this and trigger `after!' blocks at a more reasonable time."
(let ((advice-fn (intern (format "doom--defer-feature-%s-a" feature)))
(fn (or fn feature)))
`(progn
(setq features (delq ',feature features))
(advice-add #',fn :before #',advice-fn)
(defun ,advice-fn (&rest _)
;; Some plugins (like yasnippet) will invoke a fn early to parse
;; code, which would prematurely trigger this. In those cases, well
;; behaved plugins will use `delay-mode-hooks', which we can check for:
(when (and ,(intern (format "%s-hook" fn))
(not delay-mode-hooks))
;; ...Otherwise, announce to the world this package has been loaded,
;; so `after!' handlers can react.
(provide ',feature)
(advice-remove #',fn #',advice-fn))))))
(defmacro quiet! (&rest forms)
"Run FORMS without generating any output.
This silences calls to `message', `load-file', `write-region' and anything that
writes to `standard-output'."
`(cond (doom-debug-mode ,@forms)
((not doom-interactive-mode)
(let ((old-fn (symbol-function 'write-region)))
(cl-letf ((standard-output (lambda (&rest _)))
((symbol-function 'load-file) (lambda (file) (load file nil t)))
((symbol-function 'message) (lambda (&rest _)))
((symbol-function 'write-region)
(lambda (start end filename &optional append visit lockname mustbenew)
(unless visit (setq visit 'no-message))
(funcall old-fn start end filename append visit lockname mustbenew))))
,@forms)))
((let ((inhibit-message t)
(save-silently t))
(prog1 ,@forms (message ""))))))
;;
;;; Definers
(defmacro defadvice! (symbol arglist &optional docstring &rest body)
"Define an advice called SYMBOL and add it to PLACES.
@@ -419,10 +606,63 @@ DOCSTRING and BODY are as in `defun'.
where-alist))
`(progn
(defun ,symbol ,arglist ,docstring ,@body)
,(when where-alist
`(dolist (targets (list ,@(nreverse where-alist)))
(dolist (target (cdr targets))
(advice-add target (car targets) #',symbol)))))))
(dolist (targets (list ,@(nreverse where-alist)))
(dolist (target (cdr targets))
(advice-add target (car targets) #',symbol))))))
(defmacro undefadvice! (symbol _arglist &optional docstring &rest body)
"Undefine an advice called SYMBOL.
This has the same signature as `defadvice!' an exists as an easy undefiner when
testing advice (when combined with `rotate-text').
\(fn SYMBOL ARGLIST &optional DOCSTRING &rest [WHERE PLACES...] BODY\)"
(declare (doc-string 3) (indent defun))
(let (where-alist)
(unless (stringp docstring)
(push docstring body))
(while (keywordp (car body))
(push `(cons ,(pop body) (doom-enlist ,(pop body)))
where-alist))
`(dolist (targets (list ,@(nreverse where-alist)))
(dolist (target (cdr targets))
(advice-remove target #',symbol)))))
;;
;;; Backports
(when! (not EMACS27+)
;; DEPRECATED Backported from Emacs 27
(defmacro setq-local (&rest pairs)
"Make variables in PAIRS buffer-local and assign them the corresponding values.
PAIRS is a list of variable/value pairs. For each variable, make
it buffer-local and assign it the corresponding value. The
variables are literal symbols and should not be quoted.
The second VALUE is not computed until after the first VARIABLE
is set, and so on; each VALUE can use the new value of variables
set earlier in the setq-local. The return value of the
setq-local form is the value of the last VALUE.
\(fn [VARIABLE VALUE]...)"
(declare (debug setq))
(unless (zerop (mod (length pairs) 2))
(error "PAIRS must have an even number of variable/value members"))
(let ((expr nil))
(while pairs
(unless (symbolp (car pairs))
(error "Attempting to set a non-symbol: %s" (car pairs)))
;; Can't use backquote here, it's too early in the bootstrap.
(setq expr
(cons
(list 'set
(list 'make-local-variable (list 'quote (car pairs)))
(car (cdr pairs)))
expr))
(setq pairs (cdr (cdr pairs))))
(macroexp-progn (nreverse expr)))))
(provide 'core-lib)
;;; core-lib.el ends here

View File

@@ -3,7 +3,7 @@
(defvar doom-init-modules-p nil
"Non-nil if `doom-initialize-modules' has run.")
(defvar doom-modules ()
(defvar doom-modules (make-hash-table :test 'equal)
"A hash table of enabled modules. Set by `doom-initialize-modules'.")
(defvar doom-modules-dirs
@@ -11,36 +11,52 @@
doom-modules-dir)
"A list of module root directories. Order determines priority.")
(defvar doom-module-init-file "init"
"The basename of init files for modules.
Init files are loaded early, just after Doom core, and before modules' config
files. They are always loaded, even in non-interactive sessions, and before
`doom-before-init-modules-hook'. Related to `doom-module-config-file'.")
(defvar doom-module-config-file "config"
"The basename of config files for modules.
Config files are loaded later, and almost always in interactive sessions. These
run before `doom-init-modules-hook'. Relevant to `doom-module-init-file'.")
(defconst doom-obsolete-modules
'((:feature (version-control (:emacs vc) (:ui vc-gutter))
(spellcheck (:tools flyspell))
(syntax-checker (:tools flycheck))
(evil (:editor evil))
(snippets (:editor snippets))
(file-templates (:editor file-templates))
(workspaces (:ui workspaces))
(eval (:tools eval))
(lookup (:tools lookup))
(debugger (:tools debugger)))
(:tools (rotate-text (:editor rotate-text))
(vterm (:term vterm))
(password-store (:tools pass)))
(:emacs (electric-indent (:emacs electric))
(hideshow (:editor fold))
(eshell (:term eshell))
(term (:term term)))
(:ui (doom-modeline (:ui modeline))
(fci (:ui fill-column))
(evil-goggles (:ui ophints))
(tabbar (:ui tabs)))
(:app (email (:email mu4e))
(notmuch (:email notmuch))))
'((:feature (version-control (:emacs vc) (:ui vc-gutter))
(spellcheck (:checkers spell))
(syntax-checker (:checkers syntax))
(evil (:editor evil))
(snippets (:editor snippets))
(file-templates (:editor file-templates))
(workspaces (:ui workspaces))
(eval (:tools eval))
(lookup (:tools lookup))
(debugger (:tools debugger)))
(:tools (rotate-text (:editor rotate-text))
(vterm (:term vterm))
(password-store (:tools pass))
(flycheck (:checkers syntax))
(flyspell (:checkers spell)))
(:emacs (electric-indent (:emacs electric))
(hideshow (:editor fold))
(eshell (:term eshell))
(term (:term term)))
(:ui (doom-modeline (:ui modeline))
(fci (:ui fill-column))
(evil-goggles (:ui ophints))
(tabbar (:ui tabs)))
(:app (email (:email mu4e))
(notmuch (:email notmuch)))
(:lang (perl (:lang raku))))
"A tree alist that maps deprecated modules to their replacement(s).
Each entry is a three-level tree. For example:
(:feature (version-control (:emacs vc) (:ui vc-gutter))
(spellcheck (:tools flyspell))
(spellcheck (:checkers spell))
(syntax-checker (:tools flycheck)))
This marks :feature version-control, :feature spellcheck and :feature
@@ -48,7 +64,7 @@ syntax-checker modules obsolete. e.g. If :feature version-control is found in
your `doom!' block, a warning is emitted before replacing it with :emacs vc and
:ui vc-gutter.")
(defvar doom-inhibit-module-warnings doom-interactive-mode
(defvar doom-inhibit-module-warnings doom-interactive-p
"If non-nil, don't emit deprecated or missing module warnings at startup.")
;;; Custom hooks
@@ -69,28 +85,39 @@ before the user's private module.")
;;
;;; Bootstrap API
(defun doom-initialize-modules (&optional force-p)
(defun doom-initialize-core-modules ()
"Load Doom's core files for an interactive session."
(require 'core-keybinds)
(require 'core-ui)
(require 'core-projects)
(require 'core-editor))
(defun doom-module-loader (file)
"Return a closure that loads FILE from a module.
This closure takes two arguments: a cons cell containing (CATEGORY . MODULE)
symbols, and that module's plist."
(declare (pure t) (side-effect-free t))
(lambda (module plist)
(let ((doom--current-module module)
(doom--current-flags (plist-get plist :flags)))
(load! file (plist-get plist :path) t))))
(defun doom-initialize-modules (&optional force-p no-config-p)
"Loads the init.el in `doom-private-dir' and sets up hooks for a healthy
session of Dooming. Will noop if used more than once, unless FORCE-P is
non-nil."
(when (or force-p (not doom-init-modules-p))
(setq doom-init-modules-p t
doom-modules nil)
(when (load! "init" doom-private-dir t)
(when doom-modules
(maphash (lambda (key plist)
(let ((doom--current-module key)
(doom--current-flags (plist-get plist :flags)))
(load! "init" (plist-get plist :path) t)))
doom-modules))
(setq doom-init-modules-p t)
(unless no-config-p
(doom-log "Initializing core modules")
(doom-initialize-core-modules))
(when-let (init-p (load! doom-module-init-file doom-private-dir t))
(doom-log "Initializing user config")
(maphash (doom-module-loader doom-module-init-file) doom-modules)
(run-hook-wrapped 'doom-before-init-modules-hook #'doom-try-run-hook)
(when doom-interactive-mode
(when doom-modules
(maphash (lambda (key plist)
(let ((doom--current-module key)
(doom--current-flags (plist-get plist :flags)))
(load! "config" (plist-get plist :path) t)))
doom-modules))
(unless no-config-p
(maphash (doom-module-loader doom-module-config-file) doom-modules)
(run-hook-wrapped 'doom-init-modules-hook #'doom-try-run-hook)
(load! "config" doom-private-dir t)))))
@@ -101,11 +128,10 @@ non-nil."
(defun doom-module-p (category module &optional flag)
"Returns t if CATEGORY MODULE is enabled (ie. present in `doom-modules')."
(declare (pure t) (side-effect-free t))
(let ((plist (gethash (cons category module) doom-modules)))
(and plist
(or (null flag)
(memq flag (plist-get plist :flags)))
t)))
(when-let (plist (gethash (cons category module) doom-modules))
(or (null flag)
(and (memq flag (plist-get plist :flags))
t))))
(defun doom-module-get (category module &optional property)
"Returns the plist for CATEGORY MODULE. Gets PROPERTY, specifically, if set."
@@ -120,15 +146,17 @@ non-nil."
of PROPERTY and VALUEs.
\(fn CATEGORY MODULE PROPERTY VALUE &rest [PROPERTY VALUE [...]])"
(if-let (old-plist (doom-module-get category module))
(progn
(when plist
(when (cl-oddp (length plist))
(signal 'wrong-number-of-arguments (list (length plist))))
(while plist
(plist-put old-plist (pop plist) (pop plist))))
(puthash (cons category module) old-plist doom-modules))
(puthash (cons category module) plist doom-modules)))
(puthash (cons category module)
(if-let (old-plist (doom-module-get category module))
(if (null plist)
old-plist
(when (cl-oddp (length plist))
(signal 'wrong-number-of-arguments (list (length plist))))
(while plist
(plist-put old-plist (pop plist) (pop plist)))
old-plist)
plist)
doom-modules))
(defun doom-module-set (category module &rest plist)
"Enables a module by adding it to `doom-modules'.
@@ -140,10 +168,8 @@ following properties:
:path [STRING] path to category root directory
Example:
(doom-module-set :lang 'haskell :flags '(+intero))"
(puthash (cons category module)
plist
doom-modules))
(doom-module-set :lang 'haskell :flags '(+dante))"
(puthash (cons category module) plist doom-modules))
(defun doom-module-path (category module &optional file)
"Like `expand-file-name', but expands FILE relative to CATEGORY (keywordp) and
@@ -211,45 +237,108 @@ those directories. The first returned path is always `doom-private-dir'."
(declare (pure t) (side-effect-free t))
(append (list doom-private-dir)
(if module-dirs
(doom-files-in (if (listp module-dirs)
module-dirs
doom-modules-dirs)
:type 'dirs
:mindepth 1
:depth 1)
(cl-loop for plist being the hash-values of (doom-modules)
(mapcar (lambda (m) (doom-module-locate-path (car m) (cdr m)))
(delete-dups
(doom-files-in (if (listp module-dirs)
module-dirs
doom-modules-dirs)
:map #'doom-module-from-path
:type 'dirs
:mindepth 1
:depth 1)))
(cl-loop for plist being the hash-values of doom-modules
collect (plist-get plist :path)))
nil))
(defun doom-modules (&optional refresh-p)
(defun doom-module-mplist-map (fn mplist)
"Apply FN to each module in MPLIST."
(let ((mplist (copy-sequence mplist))
(inhibit-message doom-inhibit-module-warnings)
obsolete
results
category m)
(while mplist
(setq m (pop mplist))
(cond ((keywordp m)
(setq category m
obsolete (assq m doom-obsolete-modules)))
((null category)
(error "No module category specified for %s" m))
((and (listp m) (keywordp (car m)))
(pcase (car m)
(:cond
(cl-loop for (cond . mods) in (cdr m)
if (eval cond t)
return (prependq! mplist mods)))
(:if (if (eval (cadr m) t)
(push (caddr m) mplist)
(prependq! mplist (cdddr m))))
(test (if (or (eval (cadr m) t)
(eq test :unless))
(prependq! mplist (cddr m))))))
((catch 'doom-modules
(let* ((module (if (listp m) (car m) m))
(flags (if (listp m) (cdr m))))
(when-let (new (assq module obsolete))
(let ((newkeys (cdr new)))
(if (null newkeys)
(message "WARNING %s module was removed" key)
(if (cdr newkeys)
(message "WARNING %s module was removed and split into the %s modules"
(list category module) (mapconcat #'prin1-to-string newkeys ", "))
(message "WARNING %s module was moved to %s"
(list category module) (car newkeys)))
(push category mplist)
(dolist (key newkeys)
(push (if flags
(nconc (cdr key) flags)
(cdr key))
mplist)
(push (car key) mplist))
(throw 'doom-modules t))))
(push (funcall fn category module
:flags (if (listp m) (cdr m))
:path (doom-module-locate-path category module))
results))))))
(unless doom-interactive-p
(setq doom-inhibit-module-warnings t))
(nreverse results)))
(defun doom-module-list (&optional all-p)
"Minimally initialize `doom-modules' (a hash table) and return it.
This value is cached. If REFRESH-P, then don't use the cached value."
(or (unless refresh-p doom-modules)
(let (doom-interactive-mode
doom-modules
doom-init-modules-p)
(load! "init" doom-private-dir t)
(or doom-modules
(make-hash-table :test 'equal
:size 20
:rehash-threshold 1.0)))))
(if all-p
(cl-loop for path in (cdr (doom-module-load-path 'all))
collect (doom-module-from-path path))
doom-modules))
;;
;;; Use-package modifications
(eval-and-compile
(autoload 'use-package "use-package-core" nil nil t)
(setq use-package-compute-statistics doom-debug-mode
use-package-verbose doom-debug-mode
use-package-minimum-reported-time (if doom-debug-mode 0 0.1)
use-package-expand-minimally doom-interactive-mode))
(defvar doom--deferred-packages-alist '(t))
(autoload 'use-package "use-package-core" nil nil t)
(setq use-package-compute-statistics doom-debug-p
use-package-verbose doom-debug-p
use-package-minimum-reported-time (if doom-debug-p 0 0.1)
use-package-expand-minimally doom-interactive-p)
;; A common mistake for new users is that they inadvertantly install their
;; packages with package.el, by copying over old `use-package' declarations with
;; an :ensure t property. Doom doesn't use package.el, so this will throw an
;; error that will confuse beginners, so we disable `:ensure'.
(setq use-package-ensure-function #'ignore)
;; ...On the other hand, if the user has loaded `package', then we should assume
;; they know what they're doing and restore the old behavior:
(add-transient-hook! 'package-initialize
(when (eq use-package-ensure-function #'ignore)
(setq use-package-ensure-function #'use-package-ensure-elpa)))
(with-eval-after-load 'use-package-core
;; Macros are already fontified, no need for this
;; `use-package' adds syntax highlighting for the `use-package' macro, but
;; Emacs 26+ already highlights macros, so it's redundant.
(font-lock-remove-keywords 'emacs-lisp-mode use-package-font-lock-keywords)
;; We define :minor and :magic-minor from the `auto-minor-mode' package here
@@ -266,6 +355,17 @@ This value is cached. If REFRESH-P, then don't use the cached value."
(defun use-package-handler/:magic-minor (name _ arg rest state)
(use-package-handle-mode name 'auto-minor-mode-magic-alist arg rest state))
;; HACK Fix `:load-path' so it resolves relative paths to the containing file,
;; rather than `user-emacs-directory'. This is a done as a convenience
;; for users, wanting to specify a local directory.
(defadvice! doom--resolve-load-path-from-containg-file-a (orig-fn label arg &optional recursed)
"Resolve :load-path from the current directory."
:around #'use-package-normalize-paths
;; `use-package-normalize-paths' resolves paths relative to
;; `user-emacs-directory', so we change that.
(let ((user-emacs-directory (if (stringp arg) (dir!))))
(funcall orig-fn label arg recursed)))
;; Adds two keywords to `use-package' to expand its lazy-loading capabilities:
;;
;; :after-call SYMBOL|LIST
@@ -360,62 +460,16 @@ The overall load order of Doom is as follows:
Module load order is determined by your `doom!' block. See `doom-modules-dirs'
for a list of all recognized module trees. Order defines precedence (from most
to least)."
`(let ((modules
,@(if (keywordp (car modules))
(list (list 'quote modules))
modules)))
(unless doom-modules
(setq doom-modules
(make-hash-table :test 'equal
:size (if modules (length modules) 150)
:rehash-threshold 1.0)))
(let ((inhibit-message doom-inhibit-module-warnings)
obsolete category m)
(while modules
(setq m (pop modules))
(cond ((keywordp m)
(setq category m
obsolete (assq m doom-obsolete-modules)))
((not category)
(error "No module category specified for %s" m))
((and (listp m) (keywordp (car m)))
(pcase (car m)
(:cond
(cl-loop for (cond . mods) in (cdr m)
if (eval cond t)
return (prependq! modules mods)))
(:if (if (eval (cadr m) t)
(push (caddr m) modules)
(prependq! modules (cdddr m))))
(fn (if (or (eval (cadr m) t)
(eq fn :unless))
(prependq! modules (cddr m))))))
((catch 'doom-modules
(let* ((module (if (listp m) (car m) m))
(flags (if (listp m) (cdr m))))
(when-let (new (assq module obsolete))
(let ((newkeys (cdr new)))
(if (null newkeys)
(message "WARNING %s module was removed" key)
(if (cdr newkeys)
(message "WARNING %s module was removed and split into the %s modules"
(list category module) (mapconcat #'prin1-to-string newkeys ", "))
(message "WARNING %s module was moved to %s"
(list category module) (car newkeys)))
(push category modules)
(dolist (key newkeys)
(push (if flags
(nconc (cdr key) flags)
(cdr key))
modules)
(push (car key) modules))
(throw 'doom-modules t))))
(if-let (path (doom-module-locate-path category module))
(doom-module-set category module :flags flags :path path)
(message "WARNING Couldn't find the %s %s module" category module)))))))
(unless doom-interactive-mode
(setq doom-inhibit-module-warnings t))
doom-modules)))
`(unless doom-interactive-p
(doom-module-mplist-map
(lambda (category module &rest plist)
(if (plist-get plist :path)
(apply #'doom-module-set category module plist)
(message "WARNING Couldn't find the %s %s module" category module)))
,@(if (keywordp (car modules))
(list (list 'quote modules))
modules))
doom-modules))
(defvar doom-disabled-packages)
(defmacro use-package! (name &rest plist)
@@ -481,39 +535,6 @@ WARNINGS:
(lambda () ,@body)
'append)))
(defmacro require! (category module &rest flags)
"Loads the CATEGORY MODULE module with FLAGS.
CATEGORY is a keyword, MODULE is a symbol and FLAGS are symbols.
(require! :lang php +lsp)
This is for testing and internal use. This is not the correct way to enable a
module."
`(let ((doom-modules (or ,doom-modules (doom-modules)))
(module-path (doom-module-locate-path ,category ',module)))
(doom-module-set
,category ',module
(let ((plist (doom-module-get ,category ',module)))
,(when flags
`(plist-put plist :flags `,flags))
(unless (plist-member plist :path)
(plist-put plist :path ,(doom-module-locate-path category module)))
plist))
(if (directory-name-p module-path)
(condition-case-unless-debug ex
(let ((doom--current-module ',(cons category module))
(doom--current-flags ',flags))
(load! "init" module-path :noerror)
(load! "config" module-path :noerror))
('error
(lwarn 'doom-modules :error
"%s in '%s %s' -> %s"
(car ex) ,category ',module
(error-message-string ex))))
(warn 'doom-modules :warning "Couldn't find module '%s %s'"
,category ',module))))
(defmacro featurep! (category &optional module flag)
"Returns t if CATEGORY MODULE is enabled.
@@ -522,82 +543,20 @@ If FLAG is provided, returns t if CATEGORY MODULE has FLAG enabled.
(featurep! :config default)
Module FLAGs are set in your config's `doom!' block, typically in
~/.emacs.d/init.el. Like so:
~/.doom.d/init.el. Like so:
:config (default +flag1 -flag2)
CATEGORY and MODULE can be omitted When this macro is used from inside a module
(except your DOOMDIR, which is a special moduel). e.g. (featurep! +flag)"
(except your DOOMDIR, which is a special module). e.g. (featurep! +flag)"
(and (cond (flag (memq flag (doom-module-get category module :flags)))
(module (doom-module-p category module))
(doom--current-flags (memq category doom--current-flags))
((let ((module (doom-module-from-path)))
(unless module
(error "(featurep! %s %s %s) couldn't figure out what module it was called from (in %s)"
category module flag (file!)))
(memq category (doom-module-get (car module) (cdr module) :flags)))))
((if-let (module (doom-module-from-path))
(memq category (doom-module-get (car module) (cdr module) :flags))
(error "(featurep! %s %s %s) couldn't figure out what module it was called from (in %s)"
category module flag (file!)))))
t))
(defmacro after! (package &rest body)
"Evaluate BODY after PACKAGE have loaded.
PACKAGE is a symbol or list of them. These are package names, not modes,
functions or variables. It can be:
- An unquoted package symbol (the name of a package)
(after! helm BODY...)
- An unquoted list of package symbols (i.e. BODY is evaluated once both magit
and git-gutter have loaded)
(after! (magit git-gutter) BODY...)
- An unquoted, nested list of compound package lists, using any combination of
:or/:any and :and/:all
(after! (:or package-a package-b ...) BODY...)
(after! (:and package-a package-b ...) BODY...)
(after! (:and package-a (:or package-b package-c) ...) BODY...)
Without :or/:any/:and/:all, :and/:all are implied.
This is a wrapper around `eval-after-load' that:
1. Suppresses warnings for disabled packages at compile-time
2. No-ops for package that are disabled by the user (via `package!')
3. Supports compound package statements (see below)
4. Prevents eager expansion pulling in autoloaded macros all at once"
(declare (indent defun) (debug t))
(if (symbolp package)
(unless (memq package (bound-and-true-p doom-disabled-packages))
(list (if (or (not (bound-and-true-p byte-compile-current-file))
(require package nil 'noerror))
#'progn
#'with-no-warnings)
(let ((body (macroexp-progn body)))
`(if (featurep ',package)
,body
;; We intentionally avoid `with-eval-after-load' to prevent
;; eager macro expansion from pulling (or failing to pull) in
;; autoloaded macros/packages.
(eval-after-load ',package ',body)))))
(let ((p (car package)))
(cond ((not (keywordp p))
`(after! (:and ,@package) ,@body))
((memq p '(:or :any))
(macroexp-progn
(cl-loop for next in (cdr package)
collect `(after! ,next ,@body))))
((memq p '(:and :all))
(dolist (next (cdr package))
(setq body `((after! ,next ,@body))))
(car body))))))
;; DEPRECATED
(defmacro def-package! (&rest args)
(make-obsolete 'def-package! 'use-package! "2.0.9")
(message "`def-package!' was renamed to `use-package!'; use that instead.")
`(use-package! ,@args))
(defmacro def-package-hook! (&rest args)
(make-obsolete 'def-package-hook! 'use-package-hook! "2.0.9")
(message "`def-package-hook!' was renamed to `use-package-hook!'; use that instead.")
`(use-package-hook! ,@args))
(provide 'core-modules)
;;; core-modules.el ends here

View File

@@ -1,27 +1,25 @@
;;; core/core-packages.el -*- lexical-binding: t; -*-
;; Emacs package management is opinionated, and so is Doom. Doom uses `straight'
;; to create a declarative, lazy-loaded and optionally rolling-release package
;; to create a declarative, lazy-loaded and (nominally) reproducible package
;; management system. We use `straight' over `package' because the latter is
;; tempermental. ELPA sources suffer downtime occasionally, and often fail at
;; building some packages when GNU Tar is unavailable (e.g. MacOS users start
;; with BSD tar). There are also known gnutls errors in the current stable
;; release of Emacs (26.x) which bork TLS handshakes with ELPA repos (mainly
;; gnu.elpa.org). See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=3434.
;; tempermental. ELPA sources suffer downtime occasionally and often fail to
;; build packages when GNU Tar is unavailable (e.g. MacOS users start with BSD
;; tar). Known gnutls errors plague the current stable release of Emacs (26.x)
;; which bork TLS handshakes with ELPA repos (mainly gnu.elpa.org). See
;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=3434.
;;
;; What's worse, you can only get the latest version of packages through ELPA.
;; In an ecosystem that is constantly changing, this is more frustrating than
;; convenient. Straight (and Doom) can do rolling release, but it is optional
;; (and will eventually be opt-in).
;; convenient. Straight (and Doom) can do rolling release, but it is opt-in.
;;
;; ANyhow, interacting with this package management system is done through the
;; bin/doom script included with Doom Emacs. You'll find more about it by
;; running 'doom help' (I highly recommend you add it to your PATH), but here
;; are the highlights:
;; Interacting with this package management system is done through Doom's
;; bin/doom script. Find out more about it by running 'doom help' (I highly
;; recommend you add the script to your PATH). Here are some highlights:
;;
;; + `bin/doom install`: a wizard that guides you through setting up Doom and
;; your private config for the first time.
;; + `bin/doom refresh`: your go-to command for making sure Doom is in optimal
;; + `bin/doom sync`: your go-to command for making sure Doom is in optimal
;; condition. It ensures all unneeded packages are removed, all needed ones
;; are installed, and all metadata associated with them is generated.
;; + `bin/doom upgrade`: upgrades Doom Emacs and your packages to the latest
@@ -32,48 +30,31 @@
;; `doom-core-dir'. These contain `package!' declarations that tell DOOM what
;; plugins to install and where from.
;;
;; All that said, you can still use package.el's commands, but 'bin/doom
;; refresh' will purge ELPA packages.
(defvar doom-init-packages-p nil
"If non-nil, Doom's package management system has been initialized.")
;; All that said, you can still use package.el's commands, but 'bin/doom sync'
;; will purge ELPA packages.
(defvar doom-packages ()
"A list of enabled packages. Each element is a sublist, whose CAR is the
package's name as a symbol, and whose CDR is the plist supplied to its
`package!' declaration. Set by `doom-initialize-packages'.")
(defvar doom-core-packages '(straight use-package async gcmh)
"A list of packages that must be installed (and will be auto-installed if
missing) and shouldn't be deleted.")
(defvar doom-core-package-sources
'((org-elpa :local-repo nil)
(melpa
:type git :host github
:repo "melpa/melpa"
:no-build t)
(gnu-elpa-mirror
:type git :host github
:repo "emacs-straight/gnu-elpa-mirror"
:no-build t)
(emacsmirror-mirror
:type git :host github
:repo "emacs-straight/emacsmirror-mirror"
:no-build t))
"A list of recipes for straight's recipe repos.")
(defvar doom-disabled-packages ()
"A list of packages that should be ignored by `use-package!' and `after!'.")
(defvar doom-packages-file "packages"
"The basename of packages file for modules.
Package files are read whenever Doom's package manager wants a manifest of all
desired packages. They are rarely read in interactive sessions (unless the user
uses a straight or package.el command directly).")
;;
;;; Package managers
;;; package.el
;; Ensure that, if we do need package.el, it is configured correctly. You really
;; shouldn't be using it, but it may be convenient for quick package testing.
(setq package--init-file-ensured t
package-enable-at-startup nil
(setq package-enable-at-startup nil
package-user-dir (concat doom-local-dir "elpa/")
package-gnupghome-dir (expand-file-name "gpg" package-user-dir)
;; I omit Marmalade because its packages are manually submitted rather
@@ -84,175 +65,363 @@ missing) and shouldn't be deleted.")
("melpa" . ,(concat proto "://melpa.org/packages/"))
("org" . ,(concat proto "://orgmode.org/elpa/")))))
;; Don't save `package-selected-packages' to `custom-file'
(defadvice! doom--package-inhibit-custom-file-a (&optional value)
:override #'package--save-selected-packages
(if value (setq package-selected-packages value)))
;; package.el has no business modifying the user's init.el
(advice-add #'package--ensure-init-file :override #'ignore)
;; Refresh package.el the first time you call `package-install'
;; Refresh package.el the first time you call `package-install', so it can still
;; be used (e.g. to temporarily test packages). Remember to run 'doom sync' to
;; purge them; they can conflict with packages installed via straight!
(add-transient-hook! 'package-install (package-refresh-contents))
;;; straight
;;
;;; Straight
(setq straight-base-dir doom-local-dir
straight-repository-branch "develop"
straight-cache-autoloads nil ; we already do this, and better.
;; Doom doesn't encourage you to modify packages in place. Disabling this
;; makes 'doom refresh' instant (once everything set up), which is much
;; nicer UX than the several seconds modification checks.
;; makes 'doom sync' instant (once everything set up), which is much nicer
;; UX than the several seconds modification checks.
straight-check-for-modifications nil
;; We handle package.el ourselves (and a little more comprehensively)
straight-enable-package-integration nil
;; Before switching to straight, `doom-local-dir' would average out at
;; around 100mb with half Doom's modules at ~230 packages. Afterwards, at
;; around 1gb. With shallow cloning, that is reduced to ~400mb. This
;; imposes an issue with packages that require their git history for
;; certain things to work (like magit and org), but we can deal with that
;; when we cross that bridge.
;; around 1gb. With shallow cloning, that is reduced to ~400mb. This has
;; no affect on packages that are pinned, however (run 'doom purge' to
;; compact those after-the-fact). Some packages break when shallow cloned
;; (like magit and org), but we'll deal with that elsewhere.
straight-vc-git-default-clone-depth 1
;; Straight's own emacsmirror mirror is a little smaller and faster.
straight-recipes-emacsmirror-use-mirror t
;; Prefix declarations are unneeded bulk added to our autoloads file. Best
;; we just don't have to deal with them at all.
autoload-compute-prefixes nil)
;; we don't have to deal with them at all.
autoload-compute-prefixes nil
;; We handle it ourselves
straight-fix-org nil)
(defun doom--finalize-straight ()
(mapc #'funcall (delq nil (mapcar #'cdr straight--transaction-alist)))
(setq straight--transaction-alist nil))
;;; Getting straight to behave in batch mode
(when noninteractive
;; HACK Remove dired & magit options from prompt, since they're inaccessible
;; in noninteractive sessions.
(advice-add #'straight-vc-git--popup-raw :override #'straight--popup-raw))
;; HACK Replace GUI popup prompts (which hang indefinitely in tty Emacs) with
;; simple prompts.
(defadvice! doom--straight-fallback-to-y-or-n-prompt-a (orig-fn &optional prompt)
:around #'straight-are-you-sure
(if noninteractive
(y-or-n-p (format! "%s" (or prompt "")))
(funcall orig-fn prompt)))
(defadvice! doom--straight-fallback-to-tty-prompt-a (orig-fn prompt actions)
"Modifies straight to prompt on the terminal when in noninteractive sessions."
:around #'straight--popup-raw
(if (not noninteractive)
(funcall orig-fn prompt actions)
;; We can't intercept C-g, so no point displaying any options for this key
;; Just use C-c
(delq! "C-g" actions 'assoc)
;; HACK These are associated with opening dired or magit, which isn't
;; possible in tty Emacs, so...
(delq! "e" actions 'assoc)
(delq! "g" actions 'assoc)
(let ((options (list (lambda ()
(let ((doom-format-indent 0))
(terpri)
(print! (error "Aborted")))
(kill-emacs)))))
(print! (start "%s") (red prompt))
(terpri)
(print-group!
(print-group!
(print! " 1) Abort")
(dolist (action actions)
(cl-destructuring-bind (_key desc func) action
(when desc
(push func options)
(print! "%2s) %s" (length options) desc)))))
(terpri)
(let ((options (nreverse options))
answer fn)
(while
(not
(setq
fn (ignore-errors
(nth (1- (setq answer
(read-number
(format! "How to proceed? (%s) "
(mapconcat #'number-to-string
(number-sequence 1 (length options))
", ")))))
options))))
(print! (warn "%s is not a valid answer, try again.") answer))
(funcall fn))))))
(defadvice! doom--read-pinned-packages-a (orig-fn &rest args)
"Read `:pin's in `doom-packages' on top of straight's lockfiles."
:around #'straight--lockfile-read-all
(append (apply orig-fn args) ; lockfiles still take priority
(doom-package-pinned-list)))
;;
;;; Bootstrapper
;;; Bootstrappers
(defun doom--ensure-straight (recipe pin)
(let ((repo-dir (doom-path straight-base-dir "straight/repos/straight.el"))
(repo-url (concat "http" (if gnutls-verify-error "s")
"://github.com/"
(or (plist-get recipe :repo) "raxod502/straight.el")))
(branch (or (plist-get recipe :branch) straight-repository-branch))
(call (if doom-debug-p #'doom-exec-process #'doom-call-process)))
(unless (file-directory-p repo-dir)
(message "Installing straight...")
(cond
((eq straight-vc-git-default-clone-depth 'full)
(funcall call "git" "clone" "--origin" "origin" repo-url repo-dir))
((null pin)
(funcall call "git" "clone" "--origin" "origin" repo-url repo-dir
"--depth" (number-to-string straight-vc-git-default-clone-depth)
"--branch" straight-repository-branch))
((integerp straight-vc-git-default-clone-depth)
(make-directory repo-dir t)
(let ((default-directory repo-dir))
(funcall call "git" "init")
(funcall call "git" "checkout" "-b" straight-repository-branch)
(funcall call "git" "remote" "add" "origin" repo-url)
(funcall call "git" "fetch" "origin" pin
"--depth" (number-to-string straight-vc-git-default-clone-depth))
(funcall call "git" "checkout" "--detach" pin)))))
(require 'straight (concat repo-dir "/straight.el"))
(doom-log "Initializing recipes")
(with-temp-buffer
(insert-file-contents (doom-path repo-dir "bootstrap.el"))
;; Don't install straight for us -- we've already done that -- only set
;; up its recipe repos for us.
(eval-region (search-forward "(require 'straight)")
(point-max)))))
(defun doom--ensure-core-packages (packages)
(doom-log "Installing core packages")
(dolist (package packages)
(let ((name (car package)))
(when-let (recipe (plist-get (cdr package) :recipe))
(straight-override-recipe (cons name recipe)))
(straight-use-package name))))
(defun doom-initialize-core-packages (&optional force-p)
"Ensure `straight' is installed and was compiled with this version of Emacs."
(when (or force-p (null (bound-and-true-p straight-recipe-repositories)))
(doom-log "Initializing straight")
(let ((packages (doom-package-list nil 'core)))
(cl-destructuring-bind (&key recipe pin &allow-other-keys)
(alist-get 'straight packages)
(doom--ensure-straight recipe pin))
(doom--ensure-core-packages packages))))
(defun doom-initialize-packages (&optional force-p)
"Ensures that Doom's package system and straight.el are initialized.
"Process all packages, essential and otherwise, if they haven't already been.
If FORCE-P is non-nil, do it anyway.
This ensure `doom-packages' is populated, if isn't aren't already. Use this
before any of straight's or Doom's package management's API to ensure all the
necessary package metadata is initialized and available for them."
(unless doom-init-packages-p
(setq force-p t))
This ensures `doom-packages' is populated and `straight' recipes are properly
processed."
(doom-initialize-core-packages force-p)
(when (or force-p (not (bound-and-true-p package--initialized)))
(doom-log "Initializing package.el")
(require 'package)
(package-initialize))
(when (or force-p (not doom-packages))
(doom-log "Initializing straight")
(setq doom-init-packages-p t)
(unless (fboundp 'straight--reset-caches)
(doom-ensure-straight)
(require 'straight))
(straight--reset-caches)
(setq straight-recipe-repositories nil)
(mapc #'straight-use-recipes doom-core-package-sources)
(straight-register-package
`(straight :type git :host github
:repo ,(format "%s/straight.el" straight-repository-user)
:files ("straight*.el")
:branch ,straight-repository-branch
:no-byte-compile t))
(mapc #'straight-use-package doom-core-packages)
(doom-log "Initializing doom-packages")
(setq doom-disabled-packages nil
doom-packages (doom-package-list))
(cl-loop for (pkg . plist) in doom-packages
if (plist-get plist :disable)
do (cl-pushnew pkg doom-disabled-packages)
else if (not (plist-get plist :ignore))
do (with-demoted-errors "Package error: %s"
(straight-register-package
(if-let (recipe (plist-get plist :recipe))
(cons pkg recipe)
pkg))))
(unless doom-interactive-mode
(add-hook 'kill-emacs-hook #'doom--finalize-straight))))
(package-initialize)
(unless package--initialized
(error "Failed to initialize package.el")))
(when (or force-p (null doom-packages))
(doom-log "Initializing straight.el")
(or (setq doom-disabled-packages nil
doom-packages (doom-package-list))
(error "Failed to read any packages"))
(dolist (package doom-packages)
(cl-destructuring-bind
(name &key recipe disable ignore &allow-other-keys) package
(unless ignore
(if disable
(cl-pushnew name doom-disabled-packages)
(when recipe
(straight-override-recipe (cons name recipe)))
(straight-register-package name)))))))
(defun doom-ensure-straight ()
"Ensure `straight' is installed and was compiled with this version of Emacs."
(defvar bootstrap-version)
(let* (;; Force straight to install into ~/.emacs.d/.local/straight instead of
;; ~/.emacs.d/straight by pretending `doom-local-dir' is our .emacs.d.
(user-emacs-directory straight-base-dir)
(bootstrap-file (doom-path straight-base-dir "straight/repos/straight.el/straight.el"))
(bootstrap-version 5))
(make-directory (doom-path straight-base-dir "straight/build") 'parents)
(unless (featurep 'straight)
(unless (or (require 'straight nil t)
(file-readable-p bootstrap-file))
(with-current-buffer
(url-retrieve-synchronously
(format "https://raw.githubusercontent.com/raxod502/straight.el/%s/install.el"
straight-repository-branch)
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil t))))
;;
;;; Package management API
(defun doom-package-get (package &optional prop nil-value)
"Returns PACKAGE's `package!' recipe from `doom-packages'."
(let ((plist (cdr (assq package doom-packages))))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
(defun doom-package-set (package prop value)
"Set PROPERTY in PACKAGE's recipe to VALUE."
(setf (alist-get package doom-packages)
(plist-put (alist-get package doom-packages)
prop value)))
(defun doom-package-recipe (package &optional prop nil-value)
"Returns the `straight' recipe PACKAGE was registered with."
(let ((plist (gethash (symbol-name package) straight--recipe-cache)))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
(defun doom-package-recipe-repo (package)
"Resolve and return PACKAGE's (symbol) local-repo property."
(if-let* ((recipe (cdr (straight-recipes-retrieve package)))
(repo (straight-vc-local-repo-name recipe)))
repo
(symbol-name package)))
(defun doom-package-build-recipe (package &optional prop nil-value)
"Returns the `straight' recipe PACKAGE was installed with."
(let ((plist (nth 2 (gethash (symbol-name package) straight--build-cache))))
(if prop
(if (plist-member plist prop)
(plist-get plist prop)
nil-value)
plist)))
(defun doom-package-build-time (package)
"TODO"
(car (gethash (symbol-name package) straight--build-cache)))
(defun doom-package-dependencies (package &optional recursive _noerror)
"Return a list of dependencies for a package."
(let ((deps (nth 1 (gethash (symbol-name package) straight--build-cache))))
(if recursive
(append deps (mapcan (lambda (dep) (doom-package-dependencies dep t t))
(copy-sequence deps)))
(copy-sequence deps))))
(defun doom-package-depending-on (package &optional noerror)
"Return a list of packages that depend on the package named NAME."
(cl-check-type name symbol)
;; can't get dependencies for built-in packages
(unless (or (doom-package-build-recipe name)
noerror)
(error "Couldn't find %s, is it installed?" name))
(cl-loop for pkg in (hash-table-keys straight--build-cache)
for deps = (doom-package-dependencies pkg)
if (memq package deps)
collect pkg
and append (doom-package-depending-on pkg t)))
;;; Predicate functions
(defun doom-package-built-in-p (package)
"Return non-nil if PACKAGE (a symbol) is built-in."
(eq (doom-package-build-recipe package :type)
'built-in))
(defun doom-package-installed-p (package)
"Return non-nil if PACKAGE (a symbol) is installed."
(file-directory-p (straight--build-dir (symbol-name package))))
(defun doom-package-is-type-p (package type)
"TODO"
(memq type (doom-enlist (doom-package-get package :type))))
(defun doom-package-in-module-p (package category &optional module)
"Return non-nil if PACKAGE was installed by the user's private config."
(when-let (modules (doom-package-get package :modules))
(or (and (not module) (assq :private modules))
(member (cons category module) modules))))
(defun doom-package-backend (package)
"Return 'straight, 'builtin, 'elpa or 'other, depending on how PACKAGE is
installed."
(cond ((gethash (symbol-name package) straight--build-cache)
'straight)
((or (doom-package-built-in-p package)
(assq package package--builtins))
'builtin)
((assq package package-alist)
'elpa)
((locate-library (symbol-name package))
'other)))
(defun doom-package-changed-recipe-p (name)
"Return t if a package named NAME (a symbol) has a different recipe than it
was installed with."
(cl-check-type name symbol)
;; TODO
;; (when (doom-package-installed-p name)
;; (when-let* ((doom-recipe (assq name doom-packages))
;; (install-recipe (doom-package-recipe)))
;; (not (equal (cdr quelpa-recipe)
;; (cdr (plist-get (cdr doom-recipe) :recipe))))))
)
;;; Package getters
(defun doom--read-packages (file &optional noeval noerror)
(condition-case-unless-debug e
(with-temp-buffer ; prevent buffer-local state from propagating
(if (not noeval)
(load file noerror 'nomessage 'nosuffix)
(when (file-exists-p file)
(insert-file-contents file)
(let (emacs-lisp-mode) (emacs-lisp-mode))
;; Scrape `package!' blocks from FILE for a comprehensive listing of
;; packages used by this module.
(while (search-forward "(package!" nil t)
(let ((ppss (save-excursion (syntax-ppss))))
;; Don't collect packages in comments or strings
(unless (or (nth 3 ppss)
(nth 4 ppss))
(goto-char (match-beginning 0))
(cl-destructuring-bind (_ name . plist)
(read (current-buffer))
(push (cons
name (plist-put
plist :modules
(list (doom-module-from-path file))))
doom-packages))))))))
(error
(signal 'doom-package-error
(list (doom-module-from-path file)
file e)))))
(defun doom-package-list (&optional all-p core-only-p)
"Retrieve a list of explicitly declared packages from enabled modules.
If ALL-P, gather packages unconditionally across all modules, including disabled
ones."
(let ((packages-file (concat doom-packages-file ".el"))
doom-packages)
(doom--read-packages
(doom-path doom-core-dir packages-file) all-p 'noerror)
(unless core-only-p
(let ((private-packages (doom-path doom-private-dir packages-file))
(doom-modules (doom-module-list)))
(if all-p
(mapc #'doom--read-packages
(doom-files-in doom-modules-dir
:depth 2
:match "/packages\\.el$"))
;; We load the private packages file twice to ensure disabled packages
;; are seen ASAP, and a second time to ensure privately overridden
;; packages are properly overwritten.
(doom--read-packages private-packages nil 'noerror)
(cl-loop for key being the hash-keys of doom-modules
for path = (doom-module-path (car key) (cdr key) packages-file)
for doom--current-module = key
do (doom--read-packages path nil 'noerror)))
(doom--read-packages private-packages all-p 'noerror)))
(cl-remove-if-not
(if core-only-p
(lambda (pkg) (eq (plist-get (cdr pkg) :type) 'core))
#'identity)
(nreverse doom-packages))))
(defun doom-package-pinned-list ()
"Return an alist mapping package names (strings) to pinned commits (strings)."
(let (alist)
(dolist (package doom-packages alist)
(cl-destructuring-bind (name &key disable ignore pin unpin &allow-other-keys)
package
(when (and (not ignore)
(not disable)
(or pin unpin))
(setf (alist-get (doom-package-recipe-repo name) alist
nil 'remove #'equal)
(unless unpin pin)))))))
(defun doom-package-unpinned-list ()
"Return an alist mapping package names (strings) to pinned commits (strings)."
(let (alist)
(dolist (package doom-packages alist)
(cl-destructuring-bind
(_ &key recipe disable ignore pin unpin &allow-other-keys)
package
(when (and (not ignore)
(not disable)
(or unpin
(and (plist-member recipe :pin)
(null pin))))
(cl-pushnew (doom-package-recipe-repo (car package)) alist
:test #'equal))))))
(defun doom-package-recipe-list ()
"Return straight recipes for non-builtin packages with a local-repo."
(let (recipes)
(dolist (recipe (hash-table-values straight--recipe-cache))
(cl-destructuring-bind (&key local-repo type &allow-other-keys)
recipe
(unless (or (null local-repo)
(eq type 'built-in))
(push recipe recipes))))
(nreverse recipes)))
(defun doom-package-state-list ()
"TODO"
(let (alist)
(dolist (recipe (hash-table-values straight--repo-cache) alist)
(cl-destructuring-bind (&key local-repo type &allow-other-keys)
recipe
(when (and local-repo (not (eq type 'built-in)))
(setf (alist-get local-repo alist nil nil #'equal)
(straight-vc-get-commit type local-repo)))))))
;;
;;; Module package macros
(cl-defmacro package!
(name &rest plist &key built-in recipe ignore _disable _freeze)
(name &rest plist &key built-in recipe ignore _type _pin _disable)
"Declares a package and how to install it (if applicable).
This macro is declarative and does not load nor install packages. It is used to
@@ -263,67 +432,134 @@ Only use this macro in a module's packages.el file.
Accepts the following properties:
:type core|local|built-in|virtual
Specifies what kind of package this is. Can be a symbol or a list thereof.
`core' = this is a protected package and cannot be disabled!
`local' = this package is being modified in-place. This package's repo is
unshallowed and will be skipped when you update packages.
`built-in' = this package is already built-in (otherwise, will be
installed)
`virtual' = this package is not tracked by Doom's package manager. It won't
be installed or uninstalled. Use this to pin 2nd order dependencies.
:recipe RECIPE
Takes a MELPA-style recipe (see `quelpa-recipe' in `quelpa' for an example);
for packages to be installed from external sources.
Specifies a straight.el recipe to allow you to acquire packages from external
sources. See https://github.com/raxod502/straight.el#the-recipe-format for
details on this recipe.
:disable BOOL
Do not install or update this package AND disable all of its `use-package!'
blocks.
and `after!' blocks.
:ignore FORM
Do not install this package.
:freeze FORM
Do not update this package if FORM is non-nil.
:built-in BOOL
Same as :ignore if the package is a built-in Emacs package. If set to
'prefer, will use built-in package if it is present.
:pin STR|nil
Pin this package to commit hash STR. Setting this to nil will unpin this
package if previously pinned.
:built-in BOOL|'prefer
Same as :ignore if the package is a built-in Emacs package. This is more to
inform help commands like `doom/help-packages' that this is a built-in
package. If set to 'prefer, the package will not be installed if it is
already provided by Emacs.
Returns t if package is successfully registered, and nil if it was disabled
elsewhere."
(declare (indent defun))
(when (and recipe (keywordp (car-safe recipe)))
(plist-put! plist :recipe `(quote ,recipe)))
;; :built-in t is basically an alias for :ignore (locate-library NAME)
(when built-in
(when (and (not ignore) (equal built-in '(quote prefer)))
(when (and (not ignore)
(equal built-in '(quote prefer)))
(setq built-in `(locate-library ,(symbol-name name) nil doom--initial-load-path)))
(plist-delete! plist :built-in)
(plist-put! plist :ignore built-in))
`(let* ((name ',name)
(plist (cdr (assq name doom-packages))))
;; Record what module this declaration was found in
(let ((module-list (plist-get plist :modules))
(module ',(doom-module-from-path)))
(unless (member module module-list)
(plist-put! plist :modules
(append module-list
(list module)
(when (file-in-directory-p ,(dir!) doom-private-dir)
'((:private . modules)))
nil))))
;; Merge given plist with pre-existing one
(doplist! ((prop val) (list ,@plist) plist)
(unless (null val)
(plist-put! plist prop val)))
;; Some basic key validation; error if you're not using a valid key
;; Some basic key validation; throws an error on invalid properties
(condition-case e
(cl-destructuring-bind
(&key _local-repo _files _flavor _no-build
_type _repo _host _branch _remote _nonrecursive _fork _depth)
(plist-get plist :recipe))
(when-let (recipe (plist-get plist :recipe))
(cl-destructuring-bind
(&key local-repo _files _flavor
_no-build _no-byte-compile _no-autoloads
_type _repo _host _branch _remote _nonrecursive _fork _depth)
recipe
;; Expand :local-repo from current directory
(when local-repo
(plist-put!
plist :recipe
(plist-put recipe :local-repo
(let ((local-path (expand-file-name local-repo ,(dir!))))
(if (file-directory-p local-path)
local-path
local-repo)))))))
(error
(signal 'doom-package-error
(cons ,(symbol-name name)
(error-message-string e)))))
;; This is the only side-effect of this macro!
(setf (alist-get name doom-packages) plist)
(if (not (plist-get plist :disable)) t
(doom-log "Disabling package %S" name)
(cl-pushnew name doom-disabled-packages)
nil)))
(unless (plist-get plist :disable)
(with-no-warnings
(cons name plist)))))
(defmacro disable-packages! (&rest packages)
"A convenience macro for disabling packages in bulk.
Only use this macro in a module's (or your private) packages.el file."
(macroexp-progn
(cl-loop for p in packages
collect `(package! ,p :disable t))))
(mapcar (lambda (p) `(package! ,p :disable t))
packages)))
(defmacro unpin! (&rest targets)
"Unpin packages in TARGETS.
This unpins packages, so that 'doom upgrade' downloads their latest version. It
can be used one of five ways:
+ To disable pinning wholesale: (unpin! t)
+ To unpin individual packages: (unpin! packageA packageB ...)
+ To unpin all packages in a group of modules: (unpin! :lang :tools ...)
+ To unpin packages in individual modules:
(unpin! (:lang python javascript) (:tools docker))
Or any combination of the above.
This macro should only be used from the user's private packages.el. No module
should use it!"
(if (memq t targets)
`(mapc (doom-rpartial #'doom-package-set :unpin t)
(mapcar #'car doom-packages))
(macroexp-progn
(mapcar
(lambda (target)
(when target
`(doom-package-set ',target :unpin t)))
(cl-loop for target in targets
if (or (keywordp target) (listp target))
append
(cl-loop with (category . modules) = (doom-enlist target)
for (name . plist) in doom-packages
for pkg-modules = (plist-get plist :modules)
if (and (assq category pkg-modules)
(or (null modules)
(cl-loop for module in modules
if (member (cons category module) pkg-modules)
return t))
name)
collect it)
else if (symbolp target)
collect target)))))
(provide 'core-packages)
;;; core-packages.el ends here

View File

@@ -15,23 +15,18 @@ Emacs.")
"fd")
"name of `fd-find' executable binary")
(defvar doom-projectile-cache-timer-file (concat doom-cache-dir "projectile.timers")
"Where to save project file cache timers.")
;;
;;; Packages
(use-package! projectile
:after-call after-find-file dired-before-readin-hook minibuffer-setup-hook
:commands (projectile-project-root
projectile-project-name
projectile-project-p
projectile-locate-dominating-file)
:init
(setq projectile-cache-file (concat doom-cache-dir "projectile.cache")
projectile-enable-caching doom-interactive-mode
projectile-files-cache-expire 86400 ; expire after a day
projectile-enable-caching doom-interactive-p
projectile-globally-ignored-files '(".DS_Store" "Icon
" "TAGS")
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o")
@@ -44,28 +39,47 @@ Emacs.")
:config
(projectile-mode +1)
;; Projectile runs four functions to determine the root (in this order):
;;
;; + `projectile-root-local' -> checks the `projectile-project-root' variable
;; for an explicit path.
;; + `projectile-root-bottom-up' -> searches from / to your current directory
;; for the paths listed in `projectile-project-root-files-bottom-up'. This
;; includes .git and .project
;; + `projectile-root-top-down' -> searches from the current directory down to
;; / the paths listed in `projectile-root-files', like package.json,
;; setup.py, or Cargo.toml
;; + `projectile-root-top-down-recurring' -> searches from the current
;; directory down to / for a directory that has one of
;; `projectile-project-root-files-top-down-recurring' but doesn't have a
;; parent directory with the same file.
;;
;; In the interest of performance, we reduce the number of project root marker
;; files/directories projectile searches for when resolving the project root.
(setq projectile-project-root-files-bottom-up
(setq projectile-project-root-files-bottom-up
(append '(".projectile" ; projectile's root marker
".project" ; doom project marker
".git") ; Git VCS root dir
(when (executable-find "hg")
'(".hg")) ; Mercurial VCS root dir
(when (executable-find "fossil")
'(".fslckout" ; Fossil VCS root dir
'(".hg")) ; Mercurial VCS root dir
(when (executable-find "bzr")
'(".bzr")) ; Bazaar VCS root dir
(when (executable-find "darcs")
(when (executable-find "bzr")
'(".bzr"))) ; Bazaar VCS root dir
;; This will be filled by other modules. We build this list manually so
;; projectile doesn't perform so many file checks every time it resolves
;; a project's root -- particularly when a file has no project.
projectile-project-root-files '("TAGS")
;; a project's root -- particularly when a file has no project.
projectile-project-root-files '()
projectile-project-root-files-top-down-recurring '("Makefile"))
(push (abbreviate-file-name doom-local-dir) projectile-globally-ignored-directories)
;; Override projectile's dirconfig file '.projectile' with doom's project marker '.project'.
(defadvice! doom--projectile-dirconfig-file-a ()
:override #'projectile-dirconfig-file
(cond
;; Prefers '.projectile' to maintain compatibility with existing projects.
((file-exists-p! (or ".projectile" ".project") (projectile-project-root)))
((expand-file-name ".project" (projectile-project-root)))))
;; Disable commands that won't work, as is, and that Doom already provides a
;; better alternative for.
@@ -88,7 +102,7 @@ b) represent blacklisted directories that are too big, change too often or are
b) represent blacklisted directories that are too big, change too often or are
private. (see `doom-projectile-cache-blacklist'),
c) are not valid projectile projects."
(when (and (bound-and-true-p projectile-projects-cache)
(when (and (bound-and-true-p projectile-projects-cache)
doom-interactive-p)
(cl-loop with blacklist = (mapcar #'file-truename doom-projectile-cache-blacklist)
for proot in (hash-table-keys projectile-projects-cache)
@@ -116,15 +130,34 @@ c) are not valid projectile projects."
(append projectile-project-root-files-bottom-up
projectile-project-root-files)
projectile-project-root-files-bottom-up nil)))
;; HACK Don't rely on VCS-specific commands to generate our file lists. That's
;; 7 commands to maintain, versus the more generic, reliable and
;; performant `fd' or `ripgrep'.
(defadvice! doom--only-use-generic-command-a (orig-fn vcs)
"Only use `projectile-generic-command' for indexing project files.
And if it's a function, evaluate it."
:around #'projectile-get-ext-command
(if (functionp projectile-generic-command)
(funcall projectile-generic-command vcs)
projectile-generic-command))
(cond
;; If fd exists, use it for git and generic projects. fd is a rust program
;; that is significantly faster than git ls-files or find, and it respects
;; .gitignore. This is recommended in the projectile docs.
((executable-find doom-projectile-fd-binary)
(setq projectile-generic-command
(format "%s . --color=never --type f -0 -H -E .git"
doom-projectile-fd-binary)
(setq projectile-generic-command
;; `projectile-generic-command' doesn't typically support a function.
;; My `doom--only-use-generic-command-a' advice allows this. I do it
;; this way so that future changes to
;; `projectile-globally-ignored-directories' are respected.
(lambda (_)
(concat (format "%s . -0 -H -E .git --color=never --type file --type symlink --follow"
doom-projectile-fd-binary)
(cl-loop for dir in projectile-globally-ignored-directories
concat " -E "
concat (shell-quote-argument dir))
(if IS-WINDOWS " --path-separator=//")))
projectile-git-submodule-command nil
;; ensure Windows users get fd's benefits
@@ -132,29 +165,21 @@ c) are not valid projectile projects."
;; Otherwise, resort to ripgrep, which is also faster than find
((executable-find "rg")
(setq projectile-generic-command
(concat "rg -0 --files --color=never --hidden"
(cl-loop for dir in projectile-globally-ignored-directories
concat (format " --glob '!%s'" dir)))
(setq projectile-generic-command
(lambda (_)
(concat "rg -0 --files --follow --color=never --hidden"
(cl-loop for dir in projectile-globally-ignored-directories
concat " --glob "
concat (shell-quote-argument (concat "!" dir)))
(if IS-WINDOWS " --path-separator //")))
projectile-git-submodule-command nil
;; ensure Windows users get rg's benefits
projectile-indexing-method 'alien))
;; Fix breakage on windows in git projects with submodules, since Windows
;; doesn't have tr
;; doesn't have tr
(IS-WINDOWS
(setq projectile-git-submodule-command nil)))
(defadvice! doom--projectile-cache-timers-a ()
"Persist `projectile-projects-cache-time' across sessions, so that
`projectile-files-cache-expire' checks won't reset when restarting Emacs."
:before #'projectile-serialize-cache
(projectile-serialize projectile-projects-cache-time doom-projectile-cache-timer-file))
;; Restore it
(when (file-readable-p doom-projectile-cache-timer-file)
(setq projectile-projects-cache-time
(projectile-unserialize doom-projectile-cache-timer-file)))
(defadvice! doom--projectile-default-generic-command-a (orig-fn &rest args)
"If projectile can't tell what kind of project you're in, it issues an error
@@ -169,12 +194,11 @@ the command instead."
;; Projectile root-searching functions can cause an infinite loop on TRAMP
;; connections, so disable them.
;; TODO Is this still necessary?
;; TODO Is this still necessary?
(defadvice! doom--projectile-locate-dominating-file-a (file _name)
"Don't traverse the file system if on a remote connection."
:around #'projectile-locate-dominating-file
(when (and (stringp file)
(not (file-remote-p file nil t)))
"Don't traverse the file system if on a remote connection."
:before-while #'projectile-locate-dominating-file
(and (stringp file)
(not (file-remote-p file nil t)))))
@@ -193,16 +217,15 @@ state are passed in.")
add-hooks
on-load
on-enter
on-exit)
"Define a project minor-mode named NAME (a symbol) and declare where and how
it is activated. Project modes allow you to configure 'sub-modes' for
major-modes that are specific to a folder, project structure, framework or
whatever arbitrary context you define. These project modes can have their own
on-exit)
"Define a project minor mode named NAME and where/how it is activated.
Project modes allow you to configure 'sub-modes' for major-modes that are
specific to a folder, project structure, framework or whatever arbitrary context
you define. These project modes can have their own settings, keymaps, hooks,
snippets, etc.
This creates NAME-hook and NAME-map as well.
A project can be enabled through .dir-locals.el too, by setting `doom-project'.
PLIST may contain any of these properties, which are all checked to see if NAME
should be activated. If they are *all* true, NAME is activated.

View File

@@ -54,6 +54,9 @@ examples.
It is recommended you don't set specify a font-size, as to inherit `doom-font's
size.")
(defvar doom-unicode-extra-fonts nil
"Fonts to inject into the unicode charset before `doom-unicode-font'.")
;;
;;; Custom hooks
@@ -85,45 +88,51 @@ size.")
(defvar doom--last-frame nil)
(defun doom-run-switch-window-hooks-h ()
(let ((gc-cons-threshold most-positive-fixnum))
(unless (or doom-inhibit-switch-window-hooks
(eq doom--last-window (selected-window))
(minibufferp))
(let ((doom-inhibit-switch-window-hooks t))
(run-hooks 'doom-switch-window-hook)
(setq doom--last-window (selected-window))))))
(unless (or doom-inhibit-switch-window-hooks
(eq doom--last-window (selected-window))
(minibufferp))
(let ((gc-cons-threshold most-positive-fixnum)
(doom-inhibit-switch-window-hooks t)
(inhibit-redisplay t))
(run-hooks 'doom-switch-window-hook)
(setq doom--last-window (selected-window)))))
(defun doom-run-switch-frame-hooks-h (&rest _)
(unless (or doom-inhibit-switch-frame-hooks
(eq doom--last-frame (selected-frame))
(frame-parameter nil 'parent-frame))
(let ((doom-inhibit-switch-frame-hooks t))
(let ((gc-cons-threshold most-positive-fixnum)
(doom-inhibit-switch-frame-hooks t))
(run-hooks 'doom-switch-frame-hook)
(setq doom--last-frame (selected-frame)))))
(defun doom-run-switch-buffer-hooks-a (orig-fn buffer-or-name &rest args)
(let ((gc-cons-threshold most-positive-fixnum))
(if (or doom-inhibit-switch-buffer-hooks
(eq (current-buffer) (get-buffer buffer-or-name))
(and (eq orig-fn #'switch-to-buffer) (car args)))
(apply orig-fn buffer-or-name args)
(let ((doom-inhibit-switch-buffer-hooks t))
(when-let (buffer (apply orig-fn buffer-or-name args))
(with-current-buffer (if (windowp buffer)
(window-buffer buffer)
buffer)
(run-hooks 'doom-switch-buffer-hook))
buffer)))))
(if (or doom-inhibit-switch-buffer-hooks
(and buffer-or-name
(eq (current-buffer)
(get-buffer buffer-or-name)))
(and (eq orig-fn #'switch-to-buffer) (car args)))
(apply orig-fn buffer-or-name args)
(let ((gc-cons-threshold most-positive-fixnum)
(doom-inhibit-switch-buffer-hooks t)
(inhibit-redisplay t))
(when-let (buffer (apply orig-fn buffer-or-name args))
(with-current-buffer (if (windowp buffer)
(window-buffer buffer)
buffer)
(run-hooks 'doom-switch-buffer-hook))
buffer))))
(defun doom-run-switch-to-next-prev-buffer-hooks-a (orig-fn &rest args)
(let ((gc-cons-threshold most-positive-fixnum))
(if doom-inhibit-switch-buffer-hooks
(apply orig-fn args)
(let ((doom-inhibit-switch-buffer-hooks t))
(when-let (buffer (apply orig-fn args))
(with-current-buffer buffer
(run-hooks 'doom-switch-buffer-hook))
buffer)))))
(if doom-inhibit-switch-buffer-hooks
(apply orig-fn args)
(let ((gc-cons-threshold most-positive-fixnum)
(doom-inhibit-switch-buffer-hooks t)
(inhibit-redisplay t))
(when-let (buffer (apply orig-fn args))
(with-current-buffer buffer
(run-hooks 'doom-switch-buffer-hook))
buffer))))
(defun doom-protect-fallback-buffer-h ()
"Don't kill the scratch buffer. Meant for `kill-buffer-query-functions'."
@@ -135,10 +144,11 @@ size.")
e.g. If you indent with spaces by default, tabs will be highlighted. If you
indent with tabs, spaces at BOL are highlighted.
Does nothing if `whitespace-mode' is already active or the current buffer is
read-only or not file-visiting."
Does nothing if `whitespace-mode' or 'global-whitespace-mode' is already
active or if the current buffer is read-only or not file-visiting."
(unless (or (eq major-mode 'fundamental-mode)
buffer-read-only
(bound-and-true-p global-whitespace-mode)
(null buffer-file-name))
(require 'whitespace)
(set (make-local-variable 'whitespace-style)
@@ -156,6 +166,10 @@ read-only or not file-visiting."
;; Simpler confirmation prompt when killing Emacs
(setq confirm-kill-emacs #'doom-quit-p)
;; Don't prompt for confirmation when we create a new file or buffer (assume the
;; user knows what they're doing).
(setq confirm-nonexistent-file-or-buffer nil)
(setq uniquify-buffer-name-style 'forward
;; no beeping or blinking please
ring-bell-function #'ignore
@@ -173,7 +187,12 @@ read-only or not file-visiting."
(setq hscroll-margin 2
hscroll-step 1
scroll-conservatively 10
;; Emacs spends too much effort recentering the screen if you scroll the
;; cursor more than N lines past window edges (where N is the settings of
;; `scroll-conservatively'). This is especially slow in larger files
;; during large-scale scrolling commands. If kept over 100, the window is
;; never automatically recentered.
scroll-conservatively 101
scroll-margin 0
scroll-preserve-screen-position t
;; Reduce cursor lag by a tiny bit by not auto-adjusting `window-vscroll'
@@ -195,12 +214,18 @@ read-only or not file-visiting."
;;
;;; Cursor
;; Don't blink the cursor, it's too distracting.
;; The blinking cursor is distracting, but also interferes with cursor settings
;; in some minor modes that try to change it buffer-locally (like treemacs) and
;; can cause freezing for folks (esp on macOS) with customized & color cursors.
(blink-cursor-mode -1)
;; Don't blink the paren matching the one at point, it's too distracting.
(setq blink-matching-paren nil)
;; Some terminals offer two different cursors: a “visible” static cursor and a
;; “very visible” blinking one. By default, Emacs uses the very visible cursor
;; and switches to it when you start or resume Emacs. If `visible-cursor' is nil
;; when Emacs starts or resumes, it uses the normal cursor.
(setq visible-cursor nil)
;; Don't stretch the cursor to fit wide characters, it is disorienting,
@@ -214,8 +239,6 @@ read-only or not file-visiting."
;; Make `next-buffer', `other-buffer', etc. ignore unreal buffers.
(push '(buffer-predicate . doom-buffer-frame-predicate) default-frame-alist)
(setq confirm-nonexistent-file-or-buffer t)
(defadvice! doom--switch-to-fallback-buffer-maybe-a (&rest _)
"Switch to `doom-fallback-buffer' if on last real buffer.
@@ -261,9 +284,6 @@ windows, switch to `doom-fallback-buffer'. Otherwise, delegate to original
(setq indicate-buffer-boundaries nil
indicate-empty-lines nil)
;; remove continuation arrow on right fringe
(delq! 'continuation fringe-indicator-alist 'assq)
;;
;;; Windows/frames
@@ -272,40 +292,53 @@ windows, switch to `doom-fallback-buffer'. Otherwise, delegate to original
(setq frame-title-format '("%b Doom Emacs")
icon-title-format frame-title-format)
;; Don't resize emacs in steps, it looks weird.
;; Don't resize windows & frames in steps; it's prohibitive to prevent the user
;; from resizing it to exact dimensions, and looks weird.
(setq window-resize-pixelwise t
frame-resize-pixelwise t)
(unless EMACS27+ ; We already do this in early-init.el
;; Disable tool and scrollbars; Doom encourages keyboard-centric workflows, so
;; these are just clutter (the scrollbar also impacts Emacs' performance).
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist))
(unless (assq 'menu-bar-lines default-frame-alist)
;; We do this in early-init.el too, but in case the user is on Emacs 26 we do
;; it here too: disable tool and scrollbars, as Doom encourages
;; keyboard-centric workflows, so these are just clutter (the scrollbar also
;; impacts performance).
(add-to-list 'default-frame-alist '(menu-bar-lines . 0))
(add-to-list 'default-frame-alist '(tool-bar-lines . 0))
(add-to-list 'default-frame-alist '(vertical-scroll-bars)))
;; Sets `ns-appearance' and `ns-transparent-titlebar' on GUI frames (and fixes
;; mismatching text color in the frame title)
(when IS-MAC
;; These are disabled directly through their frame parameters, to avoid the
;; extra work their minor modes do, but we have to unset these variables
;; ourselves, otherwise users will have to cycle them twice to re-enable them.
(setq menu-bar-mode nil
tool-bar-mode nil
scroll-bar-mode nil)
(when! IS-MAC
;; Curse Lion and its sudden but inevitable fullscreen mode!
;; NOTE Meaningless to railwaycat's emacs-mac build
(setq ns-use-native-fullscreen nil
;; Visit files opened outside of Emacs in existing frame, rather than a
;; new one
ns-pop-up-frames nil)
(setq ns-use-native-fullscreen nil)
;; Sets ns-transparent-titlebar and ns-appearance frame parameters as is
;; appropriate for the loaded theme.
;; Visit files opened outside of Emacs in existing frame, not a new one
(setq ns-pop-up-frames nil)
;; Sets `ns-transparent-titlebar' and `ns-appearance' frame parameters so
;; window borders will match the enabled theme.
(and (or (daemonp)
(display-graphic-p))
(require 'ns-auto-titlebar nil t)
(ns-auto-titlebar-mode +1))
(add-hook! 'after-make-frame-functions
(defun doom-init-menu-bar-in-gui-frames-h (frame)
"On MacOS, the menu bar isn't part of the frame. Disabling it makes MacOS
treat Emacs as a non-application window."
(when (display-graphic-p frame)
(set-frame-parameter frame 'menu-bar-lines 1)))))
;; HACK On MacOS, disabling the menu bar makes MacOS treat Emacs as a
;; non-application window -- which means it doesn't automatically capture
;; focus when it is started, among other things. We enable menu-bar-lines
;; there, but we still want it disabled in terminal frames because there
;; it activates an ugly menu bar.
(add-hook! '(window-setup-hook after-make-frame-functions)
(defun doom-init-menu-bar-in-gui-frames-h (&optional frame)
"Re-enable menu-bar-lines in GUI frames."
(when-let (frame (or frame (selected-frame)))
(when (display-graphic-p frame)
(set-frame-parameter frame 'menu-bar-lines 1))))))
;; The native border "consumes" a pixel of the fringe on righter-most splits,
;; `window-divider' does not. Available since Emacs 25.1.
@@ -314,18 +347,21 @@ treat Emacs as a non-application window."
window-divider-default-right-width 1)
(add-hook 'doom-init-ui-hook #'window-divider-mode)
;; Prompt the user for confirmation when deleting a non-empty frame
(global-set-key [remap delete-frame] #'doom/delete-frame)
;; Prompt for confirmation when deleting a non-empty frame; a last line of
;; defense against accidental loss of work.
(global-set-key [remap delete-frame] #'doom/delete-frame-with-prompt)
;; always avoid GUI
(setq use-dialog-box nil)
;; Don't display floating tooltips; display their contents in the echo-area.
(if (bound-and-true-p tooltip-mode) (tooltip-mode -1))
;; native linux tooltips are ugly
;; Don't display floating tooltips; display their contents in the echo-area,
;; because native tooltips are ugly.
(when (bound-and-true-p tooltip-mode)
(tooltip-mode -1))
;; ...especially on linux
(when IS-LINUX
(setq x-gtk-use-system-tooltips nil))
;; Favor vertical splits over horizontal ones
;; Favor vertical splits over horizontal ones. Screens are usually wide.
(setq split-width-threshold 160
split-height-threshold nil)
@@ -334,11 +370,11 @@ treat Emacs as a non-application window."
;;; Minibuffer
;; Allow for minibuffer-ception. Sometimes we need another minibuffer command
;; _while_ we're in the minibuffer.
;; while we're in the minibuffer.
(setq enable-recursive-minibuffers t)
;; Show current key-sequence in minibuffer, like vim does. Any feedback after
;; typing is better UX than no feedback at all.
;; Show current key-sequence in minibuffer ala 'set showcmd' in vim. Any
;; feedback after typing is better UX than no feedback at all.
(setq echo-keystrokes 0.02)
;; Expand the minibuffer to fit multi-line text displayed in the echo-area. This
@@ -348,7 +384,7 @@ treat Emacs as a non-application window."
max-mini-window-height 0.15)
;; Typing yes/no is obnoxious when y/n will do
(fset #'yes-or-no-p #'y-or-n-p)
(advice-add #'yes-or-no-p :override #'y-or-n-p)
;; Try really hard to keep the cursor from getting stuck in the read-only prompt
;; portion of the minibuffer.
@@ -387,42 +423,36 @@ treat Emacs as a non-application window."
(set-window-configuration doom--ediff-saved-wconf)))))
(use-package! goto-addr
:hook (text-mode . goto-address-mode)
:hook (prog-mode . goto-address-prog-mode)
:config
(define-key goto-address-highlight-keymap (kbd "RET") #'goto-address-at-point))
(use-package! hl-line
;; Highlights the current line
:hook ((prog-mode text-mode conf-mode) . hl-line-mode)
:hook ((prog-mode text-mode conf-mode special-mode) . hl-line-mode)
:config
;; Not having to render the hl-line overlay in multiple buffers offers a tiny
;; performance boost. I also don't need to see it in other buffers.
(setq hl-line-sticky-flag nil
global-hl-line-sticky-flag nil)
;; Disable `hl-line' in evil-visual mode (temporarily). `hl-line' can make the
;; selection region harder to see while in evil visual mode.
(after! evil
(defvar doom-buffer-hl-line-mode nil)
(add-hook! 'evil-visual-state-entry-hook
(defun doom-disable-hl-line-h ()
(when hl-line-mode
(setq-local doom-buffer-hl-line-mode t)
(hl-line-mode -1))))
(add-hook! 'evil-visual-state-exit-hook
(defun doom-enable-hl-line-maybe-h ()
(when doom-buffer-hl-line-mode
(hl-line-mode +1))))))
;; Temporarily disable `hl-line' when selection is active, since it doesn't
;; serve much purpose when the selection is so much more visible.
(defvar doom--hl-line-mode nil)
(add-hook! '(evil-visual-state-entry-hook activate-mark-hook)
(defun doom-disable-hl-line-h ()
(when hl-line-mode
(setq-local doom--hl-line-mode t)
(hl-line-mode -1))))
(add-hook! '(evil-visual-state-exit-hook deactivate-mark-hook)
(defun doom-enable-hl-line-maybe-h ()
(when doom--hl-line-mode
(hl-line-mode +1)))))
(use-package! winner
;; undo/redo changes to Emacs' window layout
:after-call after-find-file doom-switch-window-hook
:preface (defvar winner-dont-bind-my-keys t) ; I'll bind keys myself
:config (winner-mode +1)
:hook (doom-first-buffer . winner-mode)
:config
(appendq! winner-boring-buffers
'("*Compile-Log*" "*inferior-lisp*" "*Fuzzy Completions*"
"*Apropos*" "*Help*" "*cvs*" "*Buffer List*" "*Ibuffer*"
@@ -431,13 +461,12 @@ treat Emacs as a non-application window."
(use-package! paren
;; highlight matching delimiters
:after-call after-find-file doom-switch-buffer-hook
:hook (doom-first-buffer . show-paren-mode)
:config
(setq show-paren-delay 0.1
show-paren-highlight-openparen t
show-paren-when-point-inside-paren t
show-paren-when-point-in-periphery t)
(show-paren-mode +1))
show-paren-when-point-in-periphery t))
;;;###package whitespace
@@ -461,13 +490,31 @@ treat Emacs as a non-application window."
all-the-icons-wicon
all-the-icons-material
all-the-icons-alltheicon)
:init
(defadvice! doom--disable-all-the-icons-in-tty-a (orig-fn &rest args)
"Return a blank string in tty Emacs, which doesn't support multiple fonts."
:around #'all-the-icons-insert
(if (display-multi-font-p)
(apply orig-fn args)
"")))
:preface
(setq doom-unicode-extra-fonts
(list "Weather Icons"
"github-octicons"
"FontAwesome"
"all-the-icons"
"file-icons"
"Material Icons"))
:config
(cond ((daemonp)
(defadvice! doom--disable-all-the-icons-in-tty-a (orig-fn &rest args)
"Return a blank string in tty Emacs, which doesn't support multiple fonts."
:around '(all-the-icons-octicon all-the-icons-material
all-the-icons-faicon all-the-icons-fileicon
all-the-icons-wicon all-the-icons-alltheicon)
(if (or (not after-init-time) (display-multi-font-p))
(apply orig-fn args)
"")))
((not (display-graphic-p))
(defadvice! doom--disable-all-the-icons-in-tty-a (&rest _)
"Return a blank string for tty users."
:override '(all-the-icons-octicon all-the-icons-material
all-the-icons-faicon all-the-icons-fileicon
all-the-icons-wicon all-the-icons-alltheicon)
""))))
;;;###package hide-mode-line-mode
(add-hook! '(completion-list-mode-hook Man-mode-hook)
@@ -486,10 +533,6 @@ treat Emacs as a non-application window."
;; languages like Lisp.
(setq rainbow-delimiters-max-face-count 3)
;;;###package pos-tip
(setq pos-tip-internal-border-width 6
pos-tip-border-width 1)
;;
;;; Line numbers
@@ -497,16 +540,16 @@ treat Emacs as a non-application window."
;; Explicitly define a width to reduce computation
(setq-default display-line-numbers-width 3)
;; Show absolute line numbers for narrowed regions
;; Show absolute line numbers for narrowed regions makes it easier to tell the
;; buffer is narrowed, and where you are, exactly.
(setq-default display-line-numbers-widen t)
;; Enable line numbers in most text-editing modes
;; Enable line numbers in most text-editing modes. We avoid
;; `global-display-line-numbers-mode' because there are many special and
;; temporary modes where we don't need/want them.
(add-hook! '(prog-mode-hook text-mode-hook conf-mode-hook)
#'display-line-numbers-mode)
(defun doom-enable-line-numbers-h () (display-line-numbers-mode +1))
(defun doom-disable-line-numbers-h () (display-line-numbers-mode -1))
;;
;;; Theme & font
@@ -514,35 +557,44 @@ treat Emacs as a non-application window."
;; Underline looks a bit better when drawn lower
(setq x-underline-at-descent-line t)
;; DEPRECATED In Emacs 27
(defvar doom--prefer-theme-elc nil
"If non-nil, `load-theme' will prefer the compiled theme (unlike its default
behavior). Do not set this directly, this is let-bound in `doom-init-theme-h'.")
(defun doom-init-fonts-h ()
"Loads `doom-font'."
(cond (doom-font
(cl-pushnew
(cons 'font
(cond ((stringp doom-font) doom-font)
((fontp doom-font) (font-xlfd-name doom-font))
((signal 'wrong-type-argument (list '(fontp stringp)
doom-font)))))
default-frame-alist
:key #'car :test #'eq))
((display-graphic-p)
(setq font-use-system-font t
doom-font (face-attribute 'default :font)))))
(cond
(doom-font
(cl-pushnew
;; Avoiding `set-frame-font' because it does a lot of extra, expensive
;; work we can avoid by setting the font frame parameter instead.
(cons 'font
(cond ((stringp doom-font) doom-font)
((fontp doom-font) (font-xlfd-name doom-font))
((signal 'wrong-type-argument (list '(fontp stringp)
doom-font)))))
default-frame-alist
:key #'car :test #'eq))
((display-graphic-p)
;; We try our best to record your system font, so `doom-big-font-mode'
;; can still use it to compute a larger font size with.
(setq font-use-system-font t
doom-font (face-attribute 'default :font)))))
(defun doom-init-extra-fonts-h (&optional frame)
"Loads `doom-variable-pitch-font',`doom-serif-font' and `doom-unicode-font'."
(condition-case e
(with-selected-frame (or frame (selected-frame))
(when doom-font
(set-face-attribute 'fixed-pitch nil :font doom-font))
(when doom-serif-font
(set-face-attribute 'fixed-pitch-serif nil :font doom-serif-font))
(when doom-variable-pitch-font
(set-face-attribute 'variable-pitch nil :font doom-variable-pitch-font))
(when (and doom-unicode-font (fboundp 'set-fontset-font))
(set-fontset-font t 'unicode doom-unicode-font nil 'prepend)))
(when (fboundp 'set-fontset-font)
(dolist (font (append doom-unicode-extra-fonts (doom-enlist doom-unicode-font)))
(set-fontset-font t 'unicode font nil 'prepend))))
((debug error)
(if (string-prefix-p "Font not available: " (error-message-string e))
(lwarn 'doom-ui :warning
@@ -554,29 +606,54 @@ behavior). Do not set this directly, this is let-bound in `doom-init-theme-h'.")
"Load the theme specified by `doom-theme' in FRAME."
(when (and doom-theme (not (memq doom-theme custom-enabled-themes)))
(with-selected-frame (or frame (selected-frame))
(let ((doom--prefer-theme-elc t))
(let ((doom--prefer-theme-elc t)) ; DEPRECATED in Emacs 27
(load-theme doom-theme t)))))
(defadvice! doom--run-load-theme-hooks-a (theme &optional _no-confirm no-enable)
"Set up `doom-load-theme-hook' to run after `load-theme' is called."
:after-while #'load-theme
(unless no-enable
(setq doom-theme theme
doom-init-theme-p t)
(run-hooks 'doom-load-theme-hook)))
(defadvice! doom--load-theme-a (orig-fn theme &optional no-confirm no-enable)
"Run `doom-load-theme-hook' on `load-theme' and fix its issues.
(defadvice! doom--prefer-compiled-theme-a (orig-fn &rest args)
"Make `load-theme' prioritize the byte-compiled theme for a moderate boost in
startup (or theme switch) time, so long as `doom--prefer-theme-elc' is non-nil."
1. Disable previously enabled themes.
2. Don't let face-remapping screw up loading the new theme
(*cough*`mixed-pitch-mode').
3. Record the current theme in `doom-theme'."
:around #'load-theme
(if (or (null after-init-time)
doom--prefer-theme-elc)
(cl-letf* ((old-locate-file (symbol-function 'locate-file))
((symbol-function 'locate-file)
(lambda (filename path &optional _suffixes predicate)
(funcall old-locate-file filename path '("c" "") predicate))))
(apply orig-fn args))
(apply orig-fn args)))
;; HACK Run `load-theme' from an estranged buffer, where we can be assured
;; that buffer-local face remaps (by `mixed-pitch-mode', for instance)
;; won't interfere with changing themes.
(with-temp-buffer
(when-let (result (funcall orig-fn theme no-confirm no-enable))
(unless no-enable
(setq doom-theme theme
doom-init-theme-p t)
;; `load-theme' doesn't disable previously enabled themes, which seems
;; like what you'd want. You could always use `enable-theme' to activate
;; multiple themes instead.
(mapc #'disable-theme (remq theme custom-enabled-themes))
(run-hooks 'doom-load-theme-hook))
result)))
(when! (not EMACS27+)
;; DEPRECATED `doom--load-theme-a' handles this for us after the theme is
;; loaded, but this only works on Emacs 27+. Disabling old themes
;; must be done *before* the theme is loaded in Emacs 26.
(defadvice! doom--disable-previous-themes-a (theme &optional _no-confirm no-enable)
"Disable other themes when loading a new one."
:before #'load-theme
(unless no-enable
(mapc #'disable-theme custom-enabled-themes)))
;; DEPRECATED Not needed in Emacs 27
(defadvice! doom--prefer-compiled-theme-a (orig-fn &rest args)
"Have `load-theme' prioritize the byte-compiled theme.
This offers a moderate boost in startup (or theme switch) time, so long as
`doom--prefer-theme-elc' is non-nil."
:around #'load-theme
(if (or (null after-init-time)
doom--prefer-theme-elc)
(letf! (defun locate-file (filename path &optional _suffixes predicate)
(funcall locate-file filename path '("c" "") predicate))
(apply orig-fn args))
(apply orig-fn args))))
;;
@@ -590,9 +667,13 @@ startup (or theme switch) time, so long as `doom--prefer-theme-elc' is non-nil."
(add-hook 'after-change-major-mode-hook #'doom-highlight-non-default-indentation-h 'append)
;; Initialize custom switch-{buffer,window,frame} hooks:
;;
;; + `doom-switch-buffer-hook'
;; + `doom-switch-window-hook'
;; + `doom-switch-frame-hook'
;;
;; These should be done as late as possible, as not to prematurely trigger
;; hooks during startup.
(add-hook 'buffer-list-update-hook #'doom-run-switch-window-hooks-h)
(add-hook 'focus-in-hook #'doom-run-switch-frame-hooks-h)
(dolist (fn '(switch-to-next-buffer switch-to-prev-buffer))
@@ -617,19 +698,27 @@ startup (or theme switch) time, so long as `doom--prefer-theme-elc' is non-nil."
;;; Fixes/hacks
;; Doom doesn't support `customize' and it never will. It's a clumsy interface
;; for something that should be configured from only one place ($DOOMDIR), so we
;; disable them.
(put 'customize 'disabled "Doom doesn't support `customize', configure Emacs from $DOOMDIR/config.el instead")
;; that sets variables at a time where it can be easily and unpredictably
;; overwritten. Configure things from your $DOOMDIR instead.
(dolist (sym '(customize-option customize-browse customize-group customize-face
customize-rogue customize-saved customize-apropos
customize-changed customize-unsaved customize-variable
customize-set-value customize-customized customize-set-variable
customize-apropos-faces customize-save-variable
customize-apropos-groups customize-apropos-options
customize-changed-options customize-save-customized))
(put sym 'disabled "Doom doesn't support `customize', configure Emacs from $DOOMDIR/config.el instead"))
(put 'customize-themes 'disabled "Set `doom-theme' or use `load-theme' in $DOOMDIR/config.el instead")
;; doesn't exist in terminal Emacs; we define it to prevent errors
;; Doesn't exist in terminal Emacs, so we define it to prevent void-function
;; errors emitted from packages that blindly try to use it.
(unless (fboundp 'define-fringe-bitmap)
(fset 'define-fringe-bitmap #'ignore))
(after! whitespace
(defun doom-disable-whitespace-mode-in-childframes-a (orig-fn)
"`whitespace-mode' inundates child frames with whitspace markers, so disable
it to fix all that visual noise."
"`whitespace-mode' inundates child frames with whitespace markers, so
disable it to fix all that visual noise."
(unless (frame-parameter nil 'parent-frame)
(funcall orig-fn)))
(add-function :around whitespace-enable-predicate #'doom-disable-whitespace-mode-in-childframes-a))

View File

@@ -1,41 +1,58 @@
;;; core.el --- the heart of the beast -*- lexical-binding: t; -*-
(when (version< emacs-version "26.1")
(error "Detected Emacs %s. Doom only supports Emacs 26.1 and higher"
emacs-version))
(eval-when-compile
(when (< emacs-major-version 26)
(error "Detected Emacs v%s. Doom only supports Emacs 26 and newer"
emacs-version)))
;;
;;; Variables
(defconst doom-version "2.0.9"
"Current version of Doom Emacs.")
(defconst EMACS27+ (> emacs-major-version 26))
(defconst EMACS28+ (> emacs-major-version 27))
(defconst IS-MAC (eq system-type 'darwin))
(defconst IS-LINUX (eq system-type 'gnu/linux))
(defconst IS-WINDOWS (memq system-type '(cygwin windows-nt ms-dos)))
(defconst IS-BSD (or IS-MAC (eq system-type 'berkeley-unix)))
;; Unix tools look for HOME, but this is normally not defined on Windows.
(when (and IS-WINDOWS (null (getenv "HOME")))
(setenv "HOME" (getenv "USERPROFILE")))
;; Ensure `doom-core-dir' is in `load-path'
(add-to-list 'load-path (file-name-directory load-file-name))
(defvar doom--initial-load-path load-path)
(defvar doom--initial-process-environment process-environment)
(defvar doom--initial-exec-path exec-path)
(defvar doom--initial-file-name-handler-alist file-name-handler-alist)
;; This is consulted on every `require', `load' and various path/io functions.
;; You get a minor speed up by nooping this.
(setq file-name-handler-alist nil)
;; `file-name-handler-alist' is consulted on every `require', `load' and various
;; path/io functions. You get a minor speed up by nooping this. However, this
;; may cause problems on builds of Emacs where its site lisp files aren't
;; byte-compiled and we're forced to load the *.el.gz files (e.g. on Alpine)
(unless noninteractive
(defvar doom--initial-file-name-handler-alist file-name-handler-alist)
;; Restore `file-name-handler-alist', because it is needed for handling
;; encrypted or compressed files, among other things.
(defun doom-reset-file-handler-alist-h ()
(setq file-name-handler-alist doom--initial-file-name-handler-alist))
(add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h)
(setq file-name-handler-alist nil)
;; Restore `file-name-handler-alist', because it is needed for handling
;; encrypted or compressed files, among other things.
(defun doom-reset-file-handler-alist-h ()
;; Re-add rather than `setq', because file-name-handler-alist may have
;; changed since startup, and we want to preserve those.
(dolist (handler file-name-handler-alist)
(add-to-list 'doom--initial-file-name-handler-alist handler))
(setq file-name-handler-alist doom--initial-file-name-handler-alist))
(add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h))
;; Load the bare necessities
;; Just the bare necessities
(require 'subr-x)
(require 'cl-lib)
(require 'core-lib)
(autoload 'doom-initialize-packages "core-packages")
;;
;;; Global variables
@@ -46,13 +63,13 @@
(defvar doom-init-time nil
"The time it took, in seconds, for Doom Emacs to initialize.")
(defvar doom-debug-mode (or (getenv "DEBUG") init-file-debug)
(defvar doom-debug-p (or (getenv "DEBUG") init-file-debug)
"If non-nil, Doom will log more.
Use `doom/toggle-debug-mode' to toggle it. The --debug-init flag and setting the
DEBUG envvar will enable this at startup.")
Use `doom-debug-mode' to toggle it. The --debug-init flag and setting the DEBUG
envvar will enable this at startup.")
(defvar doom-interactive-mode (not noninteractive)
(defvar doom-interactive-p (not noninteractive)
"If non-nil, Emacs is in interactive mode.")
;;; Directories/files
@@ -110,14 +127,8 @@ whichever is found first. Must end in a slash.")
This file is responsible for informing Emacs where to find all of Doom's
autoloaded core functions (in core/autoload/*.el).")
(defconst doom-package-autoload-file (concat doom-local-dir "autoloads.pkg.el")
"Where `doom-reload-package-autoloads' stores its package autoloads.
This file is compiled from the autoloads files of all installed packages
combined.")
(defconst doom-env-file (concat doom-local-dir "env")
"The location of your envvar file, generated by `doom env refresh`.
"The location of your envvar file, generated by `doom env`.
This file contains environment variables scraped from your shell environment,
which is loaded at startup (if it exists). This is helpful if Emacs can't
@@ -127,7 +138,7 @@ users).")
;;; Custom error types
(define-error 'doom-error "Error in Doom Emacs core")
(define-error 'doom-hook-error "Error in a Doom startup hook" 'doom-error)
(define-error 'doom-autoload-error "Error in an autoloads file" 'doom-error)
(define-error 'doom-autoload-error "Error in Doom's autoloads file" 'doom-error)
(define-error 'doom-module-error "Error in a Doom module" 'doom-error)
(define-error 'doom-private-error "Error in private config" 'doom-error)
(define-error 'doom-package-error "Error with packages" 'doom-error)
@@ -136,84 +147,99 @@ users).")
;;
;;; Emacs core configuration
;; Reduce debug output, well, unless we've asked for it.
(setq debug-on-error doom-debug-mode
jka-compr-verbose doom-debug-mode)
;; lo', longer logs ahoy, so to reliably locate lapses in doom's logic later
(setq message-log-max 8192)
;; UTF-8 as the default coding system
;; Reduce debug output, well, unless we've asked for it.
(setq debug-on-error doom-debug-p
jka-compr-verbose doom-debug-p)
;; Contrary to what many Emacs users have in their configs, you really don't
;; need more than this to make UTF-8 the default coding system:
(when (fboundp 'set-charset-priority)
(set-charset-priority 'unicode)) ; pretty
(prefer-coding-system 'utf-8) ; pretty
(setq locale-coding-system 'utf-8) ; please
;; Except for the clipboard on Windows, where its contents could be in an
;; encoding that's wider than utf-8, so we let Emacs/the OS decide what encoding
;; to use.
;; The clipboard's on Windows could be in an encoding that's wider (or thinner)
;; than utf-8, so let Emacs/the OS decide what encoding to use there.
(unless IS-WINDOWS
(setq selection-coding-system 'utf-8)) ; with sugar on top
;; Disable warnings from legacy advice system. They aren't useful, and we can't
;; often do anything about them besides changing packages upstream
;; Disable warnings from legacy advice system. They aren't useful, and what can
;; we do about them, besides changing packages upstream?
(setq ad-redefinition-action 'accept)
;; Make apropos omnipotent. It's more useful this way.
(setq apropos-do-all t)
;; Don't make a second case-insensitive pass over `auto-mode-alist'. If it has
;; to, it's our (the user's) failure. One case for all!
;; A second, case-insensitive pass over `auto-mode-alist' is time wasted, and
;; indicates misconfiguration (or that the user needs to stop relying on case
;; insensitivity).
(setq auto-mode-case-fold nil)
;; Make all regexps case-sensitive by default. This favors correctness for
;; programmatical regexp searches and provides a slight performance benefit to
;; font-locking where the keywords don't let-bind `case-fold-search' themselves
;; and are already case-correct. This could break poorly written packages!
(setq-default case-fold-search nil)
;; Display the bare minimum at startup. We don't need all that noise. The
;; dashboard/empty scratch buffer is good enough.
;; Less noise at startup. The dashboard/empty scratch buffer is good enough.
(setq inhibit-startup-message t
inhibit-startup-echo-area-message user-login-name
inhibit-default-init t
;; Avoid pulling in many packages by starting the scratch buffer in
;; `fundamental-mode', rather than, say, `org-mode' or `text-mode'.
initial-major-mode 'fundamental-mode
initial-scratch-message nil)
(fset #'display-startup-echo-area-message #'ignore)
;; Get rid of "For information about GNU Emacs..." message at startup, unless
;; we're in a daemon session, where it'll say "Starting Emacs daemon." instead,
;; which isn't so bad.
(unless (daemonp)
(advice-add #'display-startup-echo-area-message :override #'ignore))
;; Emacs "updates" its ui more often than it needs to, so we slow it down
;; slightly, from 0.5s:
(setq idle-update-delay 1)
;; slightly from 0.5s:
(setq idle-update-delay 1.0)
;; Emacs is a huge security vulnerability, what with all the dependencies it
;; pulls in from all corners of the globe. Let's at least try to be more
;; discerning.
(setq gnutls-verify-error (getenv "INSECURE")
;; Emacs is essentially one huge security vulnerability, what with all the
;; dependencies it pulls in from all corners of the globe. Let's try to be at
;; least a little more discerning.
(setq gnutls-verify-error (not (getenv "INSECURE"))
gnutls-algorithm-priority
(when (boundp 'libgnutls-version)
(concat "SECURE128:+SECURE192:-VERS-ALL"
(if (and (not IS-WINDOWS)
(not (version< emacs-version "26.3"))
(>= libgnutls-version 30605))
":+VERS-TLS1.3")
":+VERS-TLS1.2"))
;; `gnutls-min-prime-bits' is set based on recommendations from
;; https://www.keylength.com/en/4/
gnutls-min-prime-bits 3072
tls-checktrust gnutls-verify-error
tls-program '("gnutls-cli --x509cafile %t -p %p %h"
;; Emacs is built with `gnutls' by default, so `tls-program' would not be
;; used in that case. Otherwise, people have reasons to not go with
;; `gnutls', we use `openssl' instead. For more details, see
;; https://redd.it/8sykl1
tls-program '("openssl s_client -connect %h:%p -CAfile %t -nbio -no_ssl3 -no_tls1 -no_tls1_1 -ign_eof"
"gnutls-cli -p %p --dh-bits=3072 --ocsp --x509cafile=%t \
--strict-tofu --priority='SECURE192:+SECURE128:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3' %h"
;; compatibility fallbacks
"gnutls-cli -p %p %h"
"openssl s_client -connect %h:%p -no_ssl2 -no_ssl3 -ign_eof"))
"gnutls-cli -p %p %h"))
;; Emacs stores authinfo in HOME and in plaintext. Let's not do that, mkay? This
;; file usually stores usernames, passwords, and other such treasures for the
;; Emacs stores `authinfo' in $HOME and in plain-text. Let's not do that, mkay?
;; This file stores usernames, passwords, and other such treasures for the
;; aspiring malicious third party.
(setq auth-sources (list (expand-file-name "authinfo.gpg" doom-etc-dir)
(setq auth-sources (list (concat doom-etc-dir "authinfo.gpg")
"~/.authinfo.gpg"))
;; Emacs on Windows frequently confuses HOME (C:\Users\<NAME>) and APPDATA,
;; causing `abbreviate-home-dir' to produce incorrect paths.
(when IS-WINDOWS
(setq abbreviated-home-dir "\\`'"))
;; Don't litter `doom-emacs-dir'
;; Don't litter `doom-emacs-dir'. We don't use `no-littering' because it's a
;; mote too opinionated for our needs.
(setq abbrev-file-name (concat doom-local-dir "abbrev.el")
async-byte-compile-log-file (concat doom-etc-dir "async-bytecomp.log")
bookmark-default-file (concat doom-etc-dir "bookmarks")
custom-file (concat doom-private-dir "init.el")
custom-file (concat doom-private-dir "config.el")
custom-theme-directory (concat doom-private-dir "themes/")
desktop-dirname (concat doom-etc-dir "desktop")
desktop-base-file-name "autosave"
desktop-base-lock-name "autosave-lock"
pcache-directory (concat doom-cache-dir "pcache/")
request-storage-directory (concat doom-cache-dir "request")
server-auth-dir (concat doom-cache-dir "server/")
shared-game-score-directory (concat doom-etc-dir "shared-game-score/")
tramp-auto-save-directory (concat doom-cache-dir "tramp-auto-save/")
tramp-backup-directory-alist backup-directory-alist
@@ -227,13 +253,21 @@ users).")
:override #'emacs-session-filename
(concat doom-cache-dir "emacs-session." session-id))
(defadvice! doom--save-enabled-commands-to-doomdir-a (orig-fn &rest args)
"When enabling a disabled command, the `put' call is written to
~/.emacs.d/init.el, which causes issues for Doom, so write it to the user's
config.el instead."
:around #'en/disable-command
(let ((user-init-file custom-file))
(apply orig-fn args)))
;;
;;; Optimizations
;; Disable bidirectional text rendering for a modest performance boost. Of
;; course, this renders Emacs unable to detect/display right-to-left languages
;; (sorry!), but for us left-to-right language speakers/writers, it's a boon.
;; Disable bidirectional text rendering for a modest performance boost. I've set
;; this to `nil' in the past, but the `bidi-display-reordering's docs say that
;; is an undefined state and suggest this to be just as good:
(setq-default bidi-display-reordering 'left-to-right
bidi-paragraph-direction 'left-to-right)
@@ -243,7 +277,8 @@ users).")
(setq highlight-nonselected-windows nil)
;; More performant rapid scrolling over unfontified regions. May cause brief
;; spells of inaccurate fontification immediately after scrolling.
;; spells of inaccurate syntax highlighting right after scrolling, which should
;; quickly self-correct.
(setq fast-but-imprecise-scrolling t)
;; Resizing the Emacs frame can be a terribly expensive part of changing the
@@ -254,30 +289,42 @@ users).")
;; Don't ping things that look like domain names.
(setq ffap-machine-p-known 'reject)
;; Font compacting can be terribly expensive, especially for rendering icon
;; fonts on Windows. Whether it has a notable affect on Linux and Mac hasn't
;; been determined, but we inhibit it there anyway.
(setq inhibit-compacting-font-caches t)
;; Performance on Windows is considerably worse than elsewhere, especially if
;; WSL is involved. We'll need everything we can get.
(when IS-WINDOWS
;; Reduce the workload when doing file IO
(setq w32-get-true-file-attributes nil)
;; Font compacting can be terribly expensive, especially for rendering icon
;; fonts on Windows. Whether it has a noteable affect on Linux and Mac hasn't
;; been determined.
(setq inhibit-compacting-font-caches t))
(setq w32-get-true-file-attributes nil ; slightly faster IO
w32-pipe-read-delay 0 ; faster ipc
w32-pipe-buffer-size (* 64 1024))) ; read more at a time (was 4K)
;; Remove command line options that aren't relevant to our current OS; means
;; slightly less to process at startup.
(unless IS-MAC (setq command-line-ns-option-alist nil))
(unless IS-LINUX (setq command-line-x-option-alist nil))
;; Delete files to trash on macOS, as an extra layer of precaution against
;; accidentally deleting wanted files.
(setq delete-by-moving-to-trash IS-MAC)
;; Adopt a sneaky garbage collection strategy of waiting until idle time to
;; collect; staving off the collector while the user is working.
(when doom-interactive-mode
(add-transient-hook! 'pre-command-hook (gcmh-mode +1))
(with-eval-after-load 'gcmh
(setq gcmh-idle-delay 10
gcmh-verbose doom-debug-mode)
(add-hook 'focus-out-hook #'gcmh-idle-garbage-collect)))
(setq gcmh-idle-delay 5
gcmh-high-cons-threshold (* 16 1024 1024) ; 16mb
gcmh-verbose doom-debug-p)
;; HACK `tty-run-terminal-initialization' is *tremendously* slow for some
;; reason. Disabling it completely could have many side-effects, so we
;; defer it until later, at which time it (somehow) runs very quickly.
(unless (daemonp)
(advice-add #'tty-run-terminal-initialization :override #'ignore)
(add-hook! 'window-setup-hook
(defun doom-init-tty-h ()
(advice-remove #'tty-run-terminal-initialization #'ignore)
(tty-run-terminal-initialization (selected-frame) nil t))))
;;
@@ -290,18 +337,14 @@ users).")
"Run MODE-local-vars-hook after local variables are initialized."
(run-hook-wrapped (intern-soft (format "%s-local-vars-hook" major-mode))
#'doom-try-run-hook))
(add-hook 'hack-local-variables-hook #'doom-run-local-var-hooks-h)
;; If the user has disabled `enable-local-variables', then
;; `hack-local-variables-hook' is never triggered, so we trigger it at the end
;; of `after-change-major-mode-hook':
(defun doom-run-local-var-hooks-if-necessary-h ()
(defun doom-run-local-var-hooks-maybe-h ()
"Run `doom-run-local-var-hooks-h' if `enable-local-variables' is disabled."
(unless enable-local-variables
(doom-run-local-var-hooks-h)))
(add-hook 'after-change-major-mode-hook
#'doom-run-local-var-hooks-if-necessary-h
'append)
;;
@@ -326,21 +369,24 @@ If you want to disable incremental loading altogether, either remove
`doom-incremental-first-idle-timer' to nil. Incremental loading does not occur
in daemon sessions (they are loaded immediately at startup).")
(defvar doom-incremental-first-idle-timer 2
(defvar doom-incremental-first-idle-timer 2.0
"How long (in idle seconds) until incremental loading starts.
Set this to nil to disable incremental loading.")
(defvar doom-incremental-idle-timer 1.5
(defvar doom-incremental-idle-timer 0.75
"How long (in idle seconds) in between incrementally loading packages.")
(defvar doom-incremental-load-immediately (daemonp)
"If non-nil, load all incrementally deferred packages immediately at startup.")
(defun doom-load-packages-incrementally (packages &optional now)
"Registers PACKAGES to be loaded incrementally.
If NOW is non-nil, load PACKAGES incrementally, in `doom-incremental-idle-timer'
intervals."
(if (not now)
(nconc doom-incremental-packages packages)
(appendq! doom-incremental-packages packages)
(while packages
(let ((req (pop packages)))
(unless (featurep req)
@@ -357,7 +403,7 @@ intervals."
t)
(push req packages))
((error debug)
(message "Failed to load '%s' package incrementally, because: %s"
(message "Failed to load %S package incrementally, because: %s"
req e)))
(if (not packages)
(doom-log "Finished incremental loading")
@@ -370,33 +416,32 @@ intervals."
"Begin incrementally loading packages in `doom-incremental-packages'.
If this is a daemon session, load them all immediately instead."
(if (daemonp)
(if doom-incremental-load-immediately
(mapc #'require (cdr doom-incremental-packages))
(when (integerp doom-incremental-first-idle-timer)
(when (numberp doom-incremental-first-idle-timer)
(run-with-idle-timer doom-incremental-first-idle-timer
nil #'doom-load-packages-incrementally
(cdr doom-incremental-packages) t))))
(add-hook 'emacs-startup-hook #'doom-load-packages-incrementally-h)
;;
;;; Custom hooks
(defvar doom-first-input-hook nil
"Transient hooks run before the first user input.")
(defvar doom-first-file-hook nil
"Transient hooks run before the first interactively opened file.")
(defvar doom-first-buffer-hook nil
"Transient hooks run before the first interactively opened buffer.")
;;
;;; Bootstrap helpers
(defun doom-try-run-hook (hook)
"Run HOOK (a hook function) with better error handling.
Meant to be used with `run-hook-wrapped'."
(doom-log "Running doom hook: %s" hook)
(condition-case e
(funcall hook)
((debug error)
(signal 'doom-hook-error (list hook e))))
;; return nil so `run-hook-wrapped' won't short circuit
nil)
(defun doom-display-benchmark-h (&optional return-p)
"Display a benchmark, showing number of packages and modules, and how quickly
they were loaded at startup.
"Display a benchmark including number of packages and modules loaded.
If RETURN-P, return the message as a string instead of displaying it."
(funcall (if return-p #'format #'message)
@@ -407,58 +452,13 @@ If RETURN-P, return the message as a string instead of displaying it."
(setq doom-init-time
(float-time (time-subtract (current-time) before-init-time))))))
(defun doom-load-autoloads-file (file &optional noerror)
"Tries to load FILE (an autoloads file).
Return t on success, nil otherwise (but logs a warning)."
(condition-case e
(load (substring file 0 -3) noerror 'nomessage)
((debug error)
(message "Autoload file error: %s -> %s" (file-name-nondirectory file) e)
nil)))
(defun doom-load-envvars-file (file &optional noerror)
"Read and set envvars from FILE.
If NOERROR is non-nil, don't throw an error if the file doesn't exist or is
unreadable. Returns the names of envvars that were changed."
(if (not (file-readable-p file))
(unless noerror
(signal 'file-error (list "Couldn't read envvar file" file)))
(let (envvars environment)
(with-temp-buffer
(save-excursion
(insert "\n")
(insert-file-contents file))
(while (re-search-forward "\n *\\([^#= \n]*\\)=" nil t)
(push (match-string 1) envvars)
(push (buffer-substring
(match-beginning 1)
(1- (or (save-excursion
(when (re-search-forward "^\\([^= ]+\\)=" nil t)
(line-beginning-position)))
(point-max))))
environment)))
(when environment
(setq process-environment
(append (nreverse environment) process-environment)
exec-path
(if (member "PATH" envvars)
(append (parse-colon-path (getenv "PATH"))
(list exec-directory))
exec-path)
shell-file-name
(if (member "SHELL" envvars)
(setq shell-file-name
(or (getenv "SHELL") shell-file-name))
shell-file-name))
envvars))))
(defun doom-initialize (&optional force-p noerror)
(defun doom-initialize (&optional force-p)
"Bootstrap Doom, if it hasn't already (or if FORCE-P is non-nil).
The bootstrap process ensures that the essential directories exist, all core
packages are installed, `doom-autoload-file' and `doom-package-autoload-file'
exist and are loaded, and that `core-packages' is auto-loaded when `package' or
`straight' are.
The bootstrap process ensures that everything Doom needs to run is set up;
essential directories exist, core packages are installed, `doom-autoload-file'
is loaded (failing if it isn't), that all the needed hooks are in place, and
that `core-packages' will load when `package' or `straight' is used.
The overall load order of Doom is as follows:
@@ -470,7 +470,7 @@ The overall load order of Doom is as follows:
Module config.el files
~/.doom.d/config.el
`doom-init-modules-hook'
`after-init-hook'
`doom-after-init-modules-hook' (`after-init-hook')
`emacs-startup-hook'
`doom-init-ui-hook'
`window-setup-hook'
@@ -480,74 +480,67 @@ for a list of all recognized module trees. Order defines precedence (from most
to least)."
(when (or force-p (not doom-init-p))
(setq doom-init-p t)
(doom-log "Initializing Doom")
;; Reset as much state as possible, so `doom-initialize' can be treated like
;; a reset function. Particularly useful for reloading the config.
;; a reset function. e.g. when reloading the config.
(setq-default exec-path doom--initial-exec-path
load-path doom--initial-load-path
process-environment doom--initial-process-environment)
;; Load shell environment, optionally generated from 'doom env'. No need to
;; do so if we're in terminal Emacs, because Emacs will correctly inherit
;; Doom caches a lot of information in `doom-autoload-file'. Module and
;; package autoloads, autodefs like `set-company-backend!', and variables
;; like `doom-modules', `doom-disabled-packages', `load-path',
;; `auto-mode-alist', and `Info-directory-list'. etc. Compiling them into
;; one place is a big reduction in startup time.
(condition-case e
;; Avoid `file-name-sans-extension' for premature optimization reasons.
;; `string-remove-suffix' is cheaper because it performs no file sanity
;; checks; just plain ol' string manipulation.
(load (string-remove-suffix ".el" doom-autoload-file)
nil 'nomessage)
(file-missing
;; If the autoloads file fails to load then the user forgot to sync, or
;; aborted a doom command midway!
(signal 'doom-error
(list "Doom is in an incomplete state"
"run 'bin/doom sync' on the command line to repair it"))))
;; Load shell environment, optionally generated from 'doom env'. No need
;; to do so if we're in terminal Emacs, where Emacs correctly inherits
;; your shell environment there.
(when (and (or (display-graphic-p)
(daemonp))
(file-exists-p doom-env-file))
(doom-load-envvars-file doom-env-file))
(if (or (display-graphic-p)
(daemonp))
(doom-load-envvars-file doom-env-file 'noerror))
;; Loads `use-package' and all the helper macros modules (and users) can use
;; to configure their packages.
(require 'core-modules)
(let (;; `doom-autoload-file' tells Emacs where to load all its functions
;; from. This includes everything in core/autoload/*.el and autoload
;; files in enabled modules.
(core-autoloads-p (doom-load-autoloads-file doom-autoload-file noerror))
;; Loads `doom-package-autoload-file', which loads a concatenated
;; package autoloads file which caches `load-path', `auto-mode-alist',
;; `Info-directory-list', and `doom-disabled-packages'. A big
;; reduction in startup time.
(pkg-autoloads-p (doom-load-autoloads-file doom-package-autoload-file noerror)))
(if (and core-autoloads-p pkg-autoloads-p (not force-p))
;; In case we want to use package.el or straight via M-x
(progn
(with-eval-after-load 'package
(require 'core-packages))
(with-eval-after-load 'straight
(require 'core-packages)
(doom-initialize-packages)))
;; There's a chance the user will want to use package.el or straight later
;; on in this interactive session. If that's the case, make sure they're
;; properly initialized when they do.
(autoload 'doom-initialize-packages "core-packages")
(autoload 'doom-initialize-core-packages "core-packages")
(with-eval-after-load 'package (require 'core-packages))
(with-eval-after-load 'straight (doom-initialize-packages))
;; Eagerly load these libraries because we may be in a session that hasn't been
;; fully initialized (e.g. where autoloads files haven't been generated or
;; `load-path' populated).
(mapc (doom-rpartial #'load nil (not doom-debug-mode) 'nosuffix)
(file-expand-wildcards (concat doom-core-dir "autoload/*.el")))
;; Bootstrap the interactive session
(add-hook! 'window-setup-hook
(add-hook 'hack-local-variables-hook #'doom-run-local-var-hooks-h)
(add-hook 'after-change-major-mode-hook #'doom-run-local-var-hooks-maybe-h 'append)
(add-hook 'doom-first-input-hook #'gcmh-mode)
(add-hook-trigger! 'doom-first-input-hook 'pre-command-hook)
(add-hook-trigger! 'doom-first-file-hook 'after-find-file 'dired-initial-position-hook)
(add-hook-trigger! 'doom-first-buffer-hook 'after-find-file 'doom-switch-buffer-hook))
(add-hook 'emacs-startup-hook #'doom-load-packages-incrementally-h)
(add-hook 'window-setup-hook #'doom-display-benchmark-h 'append)
(if doom-debug-p (doom-debug-mode +1))
;; Create all our core directories to quell file errors
(dolist (dir (list doom-local-dir
doom-etc-dir
doom-cache-dir))
(unless (file-directory-p dir)
(make-directory dir 'parents)))
;; Load core/core-*.el, the user's private init.el and their config.el
(doom-initialize-modules force-p))
;; Ensure the package management system (and straight) are ready for
;; action (and all core packages/repos are installed)
(require 'core-packages)
(doom-initialize-packages force-p))
(unless (or (and core-autoloads-p pkg-autoloads-p)
noerror)
(unless core-autoloads-p
(warn "Your Doom core autoloads file is missing"))
(unless pkg-autoloads-p
(warn "Your package autoloads file is missing"))
(signal 'doom-autoload-error (list "Run `bin/doom refresh' to generate them"))))
t))
(defun doom-initialize-core ()
"Load Doom's core files for an interactive session."
(require 'core-keybinds)
(require 'core-ui)
(require 'core-projects)
(require 'core-editor))
doom-init-p)
(provide 'core)
;;; core.el ends here

View File

@@ -2,39 +2,60 @@
;;; core/packages.el
;; core.el
(package! dotenv-mode)
(package! auto-minor-mode)
(package! auto-minor-mode :pin "17cfa1b54800fdef2975c0c0531dad34846a5065")
(package! gcmh :pin "b1bde5089169a74f62033d027e06e98cbeedd43f")
;; core-packages.el
(package! straight
:type 'core
:recipe `(:host github
:repo "raxod502/straight.el"
:branch ,straight-repository-branch
:local-repo "straight.el"
:files ("straight*.el")
:no-build t)
:pin "09cfa1b344cbeeea7da2e075df7c19262fb2b9e6")
;; core-modules.el
(package! use-package
:type 'core
:pin "d2640fec376a8458a669e7526e63e5870d875118")
;; core-ui.el
(package! all-the-icons)
(package! hide-mode-line)
(package! highlight-numbers)
(package! rainbow-delimiters)
(package! restart-emacs)
(package! all-the-icons :pin "ed8e44de4fa601309d2bba902c3b37cb73e4daa0")
(package! hide-mode-line :pin "88888825b5b27b300683e662fa3be88d954b1cea")
(package! highlight-numbers :pin "8b4744c7f46c72b1d3d599d4fb75ef8183dee307")
(package! rainbow-delimiters :pin "5125f4e47604ad36c3eb4706310fcafac729ca8c")
(package! restart-emacs :pin "9aa90d3df9e08bc420e1c9845ee3ff568e911bd9")
;; core-editor.el
(package! better-jumper)
(package! dtrt-indent)
(package! helpful)
(package! ns-auto-titlebar :ignore (not IS-MAC))
(package! pcre2el)
(package! smartparens)
(package! better-jumper :pin "6d240032ca213ccb3347e25f26c29b6822bf03a7")
(package! dtrt-indent :pin "50c440c80e0d15303d8ab543bce4c56e9c2bf407")
(package! helpful :pin "c0662aa07266fe204f4e6d72ccaa6af089400556")
(when IS-MAC
(package! ns-auto-titlebar :pin "1efc30d38509647b417f05587fd7003457719256"))
(package! pcre2el :pin "0b5b2a2c173aab3fd14aac6cf5e90ad3bf58fa7d")
(package! smartparens :pin "555626a43f9bb1985aa9a0eb675f2b88b29702c8")
(package! so-long
:built-in 'prefer
:built-in 'prefer ; included in Emacs 27+
;; REVIEW so-long is slated to be published to ELPA eventually, but until then
;; I've created my own mirror for it because git.savannah.gnu.org runs on a
;; potato.
:recipe (:host github :repo "hlissner/emacs-so-long"))
(package! undo-tree
;; Version 0.6.5 is on ELPA which lacks a fix we need, so we install 0.6.6
;; from emacsmirror/undo-tree instead.
:recipe (:host github :repo "emacsmirror/undo-tree"))
(package! ws-butler)
(package! xclip)
;; I've created my own mirror for it because git.savannah.gnu.org runs
;; on a potato.
:recipe (:host github :repo "hlissner/emacs-so-long")
:pin "ed666b0716f60e8988c455804de24b55919e71ca")
(package! ws-butler
;; Use my fork of ws-butler, which has a few choice improvements and
;; optimizations (the original has been abandoned).
:recipe (:host github :repo "hlissner/ws-butler")
:pin "2bb49d3ee7d2cba133bc7e9cdac416cd1c5e4fe0")
(unless IS-WINDOWS
(package! clipetty
:recipe (:host github :repo "spudlyo/clipetty")
:pin "01b39044b9b65fa4ea7d3166f8b1ffab6f740362"))
;; core-projects.el
(package! projectile)
(package! projectile :pin "7e552b6d876014ca5b4609318ca8a202b2a89014")
;; core-keybinds.el
(package! general)
(package! which-key)
(package! general :pin "a0b17d207badf462311b2eef7c065b884462cb7c")
(package! which-key :pin "8b49ae978cceca65967f3544c236f32964ddbed0")

View File

@@ -1,25 +1,19 @@
Before you doom yourself, there are a few things you should know:
But before you doom yourself, here are some things you should know:
1. Whenever you edit your doom! block in ~/.doom.d/init.el or modify your
modules, run:
1. Don't forget to run 'doom sync', then restart Emacs, after modifying
~/.doom.d/init.el or ~/.doom.d/packages.el.
bin/doom refresh
This command ensures needed packages are installed, orphaned packages are
removed, and your autoloads/cache files are up to date. When in doubt, run
'doom sync'!
This will ensure all needed packages are installed, all orphaned packages are
removed, and your autoloads files are up to date. This is important! If you
forget to do this you will get errors!
2. If something goes wrong, run `doom doctor`. It diagnoses common issues with
your environment and setup, and may offer clues about what is wrong.
2. If something inexplicably goes wrong, try `bin/doom doctor`
3. Use 'doom upgrade' to update Doom. Doing it any other way will require
additional steps. Run 'doom help upgrade' to understand those extra steps.
This will diagnose common issues with your environment and setup, and may
give you clues about what is wrong.
3. Use `bin/doom upgrade` to update Doom. Doing it any other way may require
additional work. When in doubt, run `bin/doom refresh`.
4. Check out `bin/doom help` to see what else `bin/doom` can do (and it is
recommended you add ~/.emacs.d/bin to your PATH).
5. You can find Doom's documentation via `M-x doom/help` or `SPC h D`.
4. Access Doom's documentation from within Emacs via 'SPC h D' or 'C-h D' (or
'M-x doom/help')
Have fun!

View File

@@ -1,69 +0,0 @@
;; -*- lexical-binding: t; no-byte-compile: t; -*-
;;; core/test/helpers.el
(eval-and-compile
(setq doom-interactive-mode 'test)
(doom-initialize 'force 'noerror)
(require 'buttercup)
(setq split-width-threshold 0
split-height-threshold 0
window-min-width 0
window-min-height 0))
;;
;;; Buttercup extensions
(buttercup-define-matcher-for-binary-function :to-equal-file file-equal-p)
(buttercup-define-matcher :to-expand-into (form expected)
(cl-destructuring-bind (form expected)
(mapcar #'funcall (list form expected))
(let ((expanded (macroexpand-1 form)))
(if (equal expanded expected)
(cons t (format "Expected `%S' to not expand to `%S'"
form expected))
(cons nil (format "Expected `%S' to not expand to `%S', but got `%S' instead"
form expected expanded))))))
(buttercup-define-matcher :to-output (form &optional expected-output)
(let ((expected-output (and (functionp expected-output)
(funcall expected-output)))
output)
(with-current-buffer (get-buffer "*Messages*")
(let ((standard-output (current-buffer))
(start (point)))
(let ((inhibit-message t))
(funcall form))
(setq output (buffer-substring-no-properties start (point-max)))
(with-silent-modifications (erase-buffer))))
(cond ((null expected-output)
(if (string-empty-p output)
(cons nil (format "Expected output %S, but got none"
expected-output))
(cons t (format "Expected no output, but got %S"
output))))
((not (equal expected-output output))
(cons nil (format "Expected output %S, but got %S instead"
expected-output output)))
((cons t (format "Expected to not get %S as output"
expected-output))))))
(buttercup-define-matcher :to-contain-items (items expected)
(cl-destructuring-bind (items expected)
(mapcar #'funcall (list items expected))
(if-let (missing (cl-set-difference expected items))
(cons nil (format "Expected list to contain %S, but it was missing %S"
expected missing))
(cons t (format "Expected list to not contain %S, but it did: %S"
expected items)))))
;;
;;; Helper macros
(defmacro insert!! (&rest text)
"Insert TEXT in buffer, then move cursor to last {0} marker."
`(progn
(insert ,@text)
(when (search-backward "{0}" nil t)
(replace-match "" t t))))

View File

@@ -1,17 +0,0 @@
;;; core/test/init.el -*- lexical-binding: t; no-byte-compile: t; -*-
;; An init.el for our unit test suites. Do not use this!
(doom! :completion
company
:ui
doom-dashboard
popup
workspaces
:editor
evil
:tools
pass
:lang
org
web)

View File

@@ -1,4 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/packages.el
(package! buttercup)

View File

@@ -3,9 +3,9 @@
(describe "core/autoload/format"
(describe "format!"
:var (doom-format-backend)
:var (doom-output-backend)
(before-all
(setq doom-format-backend 'ansi))
(setq doom-output-backend 'ansi))
(it "should be a drop-in replacement for `format'"
(expect (format! "Hello %s" "World")
@@ -16,7 +16,7 @@
:to-equal "Hello World"))
(it "supports text properties in interactive sessions"
(let ((doom-format-backend 'text-properties))
(let ((doom-output-backend 'text-properties))
(expect (get-text-property 0 'face (format! (red "Hello %s") "World"))
:to-equal (list :foreground (face-foreground 'term-color-red)))))

View File

@@ -101,12 +101,6 @@
(expect (appendq! list '(d e f))
:to-equal '(a b c d e f)))))
(describe "nconcq!"
(it "nconc's a list to a list symbol"
(let ((list '(a b c)))
(expect (nconcq! list '(d e f))
:to-equal '(a b c d e f)))))
(describe "delq!"
(it "delete's a symbol from a list"
(let ((list '(a b c)))

View File

@@ -2,9 +2,9 @@
;;; core/test/test-core.el
(describe "core"
:var (doom-interactive-mode)
:var (doom-interactive-p)
(before-each
(setq doom-interactive-mode nil))
(setq doom-interactive-p nil))
(describe "initialization"
(describe "doom-initialize"
@@ -33,7 +33,7 @@
(expect 'doom-initialize-packages :to-have-been-called))
(it "doesn't initialize packages if core autoload file was loaded"
(let ((doom-interactive-mode t))
(let ((doom-interactive-p t))
(spy-on 'doom-load-autoloads-file :and-return-value t)
(doom-initialize nil 'noerror)
(expect 'doom-load-autoloads-file :to-have-been-called-with doom-package-autoload-file)