Adding more emacs
This commit is contained in:
parent
34f1079f89
commit
83775eb45f
|
@ -0,0 +1,25 @@
|
|||
:: Forward the ./doom script to Emacs
|
||||
|
||||
@ECHO OFF
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
PUSHD "%~dp0" >NUL
|
||||
|
||||
SET args=
|
||||
SET command=%1
|
||||
|
||||
:LOOP
|
||||
SHIFT /1
|
||||
IF NOT [%1]==[] (
|
||||
SET args=%args% %1
|
||||
GOTO :LOOP
|
||||
)
|
||||
|
||||
IF [%command%]==[run] (
|
||||
start runemacs -Q %args% -l ..\init.el -f "doom-run-all-startup-hooks-h"
|
||||
) ELSE (
|
||||
emacs --quick --script .\doom -- %*
|
||||
)
|
||||
|
||||
POPD >NUL
|
||||
ECHO ON
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env sh
|
||||
:; set -e # -*- mode: emacs-lisp; lexical-binding: t -*-
|
||||
:; case "$EMACS" in *term*) EMACS=emacs ;; *) EMACS="${EMACS:-emacs}" ;; esac
|
||||
:; $EMACS --version >/dev/null 2>&1 || { >&2 echo "Can't find emacs in your PATH"; exit 1; }
|
||||
:; $EMACS --no-site-file --script "$0" -- "$@" || __DOOMCODE=$?
|
||||
:; [ "${__DOOMCODE:-0}" -eq 128 ] && { sh "`$EMACS -Q --batch --eval '(princ temporary-file-directory)'`/doom.sh" "$0" "$@" && true; __DOOMCODE=$?; }
|
||||
:; exit $__DOOMCODE
|
||||
|
||||
;; The garbage collector isn't as important during CLI ops. A higher threshold
|
||||
;; makes it 15-30% faster, but set it too high and we risk spiralling memory
|
||||
;; usage in longer sessions.
|
||||
(setq gc-cons-threshold 134217728) ; 128mb
|
||||
;; Prioritize non-byte-compiled source files in non-interactive sessions to
|
||||
;; prevent loading stale byte-code.
|
||||
(setq load-prefer-newer t)
|
||||
;; Ensure Doom runs out of this file's parent directory, where Doom is
|
||||
;; presumably installed. Use the EMACSDIR envvar to change this.
|
||||
(setq user-emacs-directory
|
||||
(if (getenv-internal "EMACSDIR")
|
||||
(file-name-as-directory (expand-file-name (getenv-internal "EMACSDIR")))
|
||||
(expand-file-name
|
||||
"../" (file-name-directory (file-truename load-file-name)))))
|
||||
|
||||
;; Handle some potential issues early
|
||||
(when (version< emacs-version "26.1")
|
||||
(error (concat "Detected Emacs %s (at %s).\n\n"
|
||||
"Doom only supports Emacs 26.1 and newer. 27.1 is highly recommended. A guide\n"
|
||||
"to install a newer version of Emacs can be found at:\n\n "
|
||||
(cond ((eq system-type 'darwin)
|
||||
"https://github.com/hlissner/doom-emacs/blob/develop/docs/getting_started.org#on-macos")
|
||||
((memq system-type '(cygwin windows-nt ms-dos))
|
||||
"https://github.com/hlissner/doom-emacs/blob/develop/docs/getting_started.org#on-windows")
|
||||
("https://github.com/hlissner/doom-emacs/blob/develop/docs/getting_started.org#on-linux"))
|
||||
"Aborting...")
|
||||
emacs-version
|
||||
(car command-line-args)))
|
||||
|
||||
(unless (file-exists-p (expand-file-name "core/core.el" user-emacs-directory))
|
||||
(error (concat "Couldn't find Doom Emacs in %S.\n\n"
|
||||
"This is likely because this script (or its parent directory) is a symlink.\n"
|
||||
"If you must use a symlink, you'll need to specify an EMACSDIR so Doom knows\n"
|
||||
"where to find itself. e.g.\n\n "
|
||||
(if (string-match-p "/fish$" (getenv "SHELL"))
|
||||
"env EMACSDIR=~/.emacs.d doom"
|
||||
"EMACSDIR=~/.emacs.d doom sync")
|
||||
"\n\n"
|
||||
"Aborting...")
|
||||
(abbreviate-file-name user-emacs-directory)
|
||||
(abbreviate-file-name load-file-name)))
|
||||
|
||||
(when (and (equal (user-real-uid) 0)
|
||||
(not (file-in-directory-p user-emacs-directory "/root")))
|
||||
(error (concat "This script is running as root. This likely wasn't intentional and\n"
|
||||
"will cause file permissions errors later if this Doom install is\n"
|
||||
"ever used on a non-root account.\n\n"
|
||||
"Aborting...")))
|
||||
|
||||
;; HACK Load `cl' and site files manually to prevent polluting logs and stdout
|
||||
;; with deprecation and/or file load messages.
|
||||
(let ((inhibit-message t))
|
||||
(when (> emacs-major-version 26)
|
||||
(require 'cl))
|
||||
(unless site-run-file
|
||||
(let ((tail load-path))
|
||||
(while tail
|
||||
(let ((default-directory (car tail)))
|
||||
(load (expand-file-name "subdirs.el") t t t)
|
||||
(setq tail (cdr tail)))))
|
||||
(load "site-start" t t)))
|
||||
|
||||
;; Load the heart of the beast and its CLI processing library
|
||||
(load (expand-file-name "core/core.el" user-emacs-directory) nil t)
|
||||
(require 'core-cli)
|
||||
|
||||
;; I use our own home-grown debugger so we can capture and store backtraces,
|
||||
;; make them more presentable, and make it easier for users to produce better
|
||||
;; bug reports!
|
||||
(setq debugger #'doom-cli--debugger
|
||||
debug-on-error t
|
||||
debug-ignored-errors nil)
|
||||
|
||||
(kill-emacs
|
||||
(pcase
|
||||
(catch 'exit
|
||||
;; Process the arguments passed to this script. `doom-cli-execute' should
|
||||
;; return a boolean, integer (error code) or throw an 'exit event, which
|
||||
;; we handle specially.
|
||||
(apply #'doom-cli-execute :doom (cdr (member "--" argv))))
|
||||
;; Any non-zero integer is treated as an explicit exit code.
|
||||
((and (pred integerp) code) code)
|
||||
;; If, instead, we were given a string or list of strings, copy these as
|
||||
;; shell script commands to a temporary script file which this script will
|
||||
;; execute after this session finishes. Also accepts special keywords, like
|
||||
;; `:restart', to rerun the current command.
|
||||
((and (or (pred consp)
|
||||
(pred stringp)
|
||||
(pred keywordp))
|
||||
command)
|
||||
(let ((script (expand-file-name "doom.sh" temporary-file-directory))
|
||||
(coding-system-for-write 'utf-8-unix)
|
||||
(coding-system-for-read 'utf-8-unix))
|
||||
(with-temp-file script
|
||||
(insert "#!/usr/bin/env sh\n"
|
||||
"_postscript() {\n"
|
||||
" rm -f " (shell-quote-argument script) "\n "
|
||||
(cond ((eq command :restart)
|
||||
"$@")
|
||||
((stringp command)
|
||||
command)
|
||||
((string-join
|
||||
(if (listp (car-safe command))
|
||||
(cl-loop for line in (doom-enlist command)
|
||||
collect (mapconcat #'shell-quote-argument (remq nil line) " "))
|
||||
(list (mapconcat #'shell-quote-argument (remq nil command) " ")))
|
||||
"\n ")))
|
||||
"\n}\n"
|
||||
(save-match-data
|
||||
(cl-loop for env
|
||||
in (cl-set-difference process-environment
|
||||
doom--initial-process-environment
|
||||
:test #'equal)
|
||||
if (string-match "^\\([a-zA-Z0-9_]+\\)=\\(.+\\)$" env)
|
||||
concat (format "%s=%s \\\n"
|
||||
(match-string 1 env)
|
||||
(shell-quote-argument (match-string 2 env)))))
|
||||
(format "PATH=\"%s%s$PATH\" \\\n" (concat doom-emacs-dir "bin/") path-separator)
|
||||
"_postscript $@\n"))
|
||||
(set-file-modes script #o600))
|
||||
;; Error code 128 is special: it means run the post-script after this
|
||||
;; session ends.
|
||||
128)
|
||||
;; Anything else (e.g. booleans) is treated as a successful run. Yes, a `nil'
|
||||
;; indicates a successful run too!
|
||||
(_ 0)))
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Open an org-capture popup frame from the shell. This opens a temporary emacs
|
||||
# daemon if emacs isn't already running.
|
||||
#
|
||||
# Usage: org-capture [-k KEY] [MESSAGE]
|
||||
# Examples:
|
||||
# org-capture -k n "To the mind that is still, the whole universe surrenders."
|
||||
|
||||
set -e
|
||||
|
||||
cleanup() {
|
||||
emacsclient --eval '(let (kill-emacs-hook) (kill-emacs))'
|
||||
}
|
||||
|
||||
# If emacs isn't running, we start a temporary daemon, solely for this window.
|
||||
if ! emacsclient --suppress-output --eval nil; then
|
||||
emacs --daemon
|
||||
trap cleanup EXIT INT TERM
|
||||
daemon=1
|
||||
fi
|
||||
|
||||
# org-capture key mapped to argument flags
|
||||
# keys=$(emacsclient -e "(+org-capture-available-keys)" | cut -d '"' -f2)
|
||||
while getopts "hk:" opt; do
|
||||
key="\"$OPTARG\""
|
||||
break
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
[ -t 0 ] && str="$*" || str=$(cat)
|
||||
|
||||
# Fix incompatible terminals that cause odd 'not a valid terminal' errors
|
||||
[ $TERM = "alacritty" ] && export TERM=xterm-256color
|
||||
|
||||
if [ $daemon ]; then
|
||||
emacsclient -a "" \
|
||||
-c -F '((name . "doom-capture") (width . 70) (height . 25) (transient . t))' \
|
||||
-e "(+org-capture/open-frame \"$str\" ${key:-nil})"
|
||||
else
|
||||
# Non-daemon servers flicker a lot if frames are created from terminal, so we
|
||||
# do it internally instead.
|
||||
emacsclient -a "" \
|
||||
-e "(+org-capture/open-frame \"$str\" ${key:-nil})"
|
||||
fi
|
|
@ -0,0 +1,159 @@
|
|||
#!/usr/bin/env sh
|
||||
":"; exec emacs --quick --script "$0" -- "$@" # -*- mode: emacs-lisp; lexical-binding: t; -*-
|
||||
;;; bin/org-tangle
|
||||
|
||||
;; Tangles source blocks from org files. Also expands #+INCLUDE directives,
|
||||
;; unlike vanilla `ob-tangle'. Debug/info messages are directed to stderr and
|
||||
;; can be ignored.
|
||||
;;
|
||||
;; -l/--lang LANG
|
||||
;; Only include blocks in the specified language (e.g. emacs-lisp).
|
||||
;; -a/--all
|
||||
;; Tangle all blocks by default (unless it has :tangle nil set or a
|
||||
;; :notangle: tag)
|
||||
;; -t/--tag TAG
|
||||
;; --and TAG
|
||||
;; --or TAG
|
||||
;; Only include blocks in trees that have these tags. Combine multiple --and
|
||||
;; and --or's, or just use --tag (implicit --and).
|
||||
;; -p/--print
|
||||
;; Prints tangled code to stdout instead of to files
|
||||
;;
|
||||
;; Usage: org-tangle [[-l|--lang] LANG] some-file.org another.org
|
||||
;; Examples:
|
||||
;; org-tangle -l sh modules/some/module/README.org > install_module.sh
|
||||
;; org-tangle -l sh modules/lang/go/README.org | sh
|
||||
;; org-tangle --and tagA --and tagB my/literate/config.org
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'ox)
|
||||
(require 'ob-tangle)
|
||||
|
||||
(defun usage ()
|
||||
(with-temp-buffer
|
||||
(insert (format "%s %s [OPTIONS] [TARGETS...]\n"
|
||||
"[1mUsage:[0m"
|
||||
(file-name-nondirectory load-file-name))
|
||||
"\n"
|
||||
"A command line interface for tangling org-mode files. TARGETS can be\n"
|
||||
"files or folders (which are searched for org files recursively).\n"
|
||||
"\n"
|
||||
"This is useful for literate configs that rely on command line\n"
|
||||
"workflows to build it.\n"
|
||||
"\n"
|
||||
"[1mExample:[0m\n"
|
||||
" org-tangle some-file.org\n"
|
||||
" org-tangle literate/config/\n"
|
||||
" org-tangle -p -l sh scripts.org > do_something.sh\n"
|
||||
" org-tangle -p -l python -t tagA -t tagB file.org | python\n"
|
||||
"\n"
|
||||
"[1mOptions:[0m\n"
|
||||
" -a --all\t\tTangle all blocks by default\n"
|
||||
" -l --lang LANG\tOnly tangle blocks written in LANG\n"
|
||||
" -p --print\t\tPrint tangled output to stdout than to files\n"
|
||||
" -t --tag TAG\n"
|
||||
" --and TAG\n"
|
||||
" --or TAG\n"
|
||||
" Lets you tangle org blocks by tag. You may have more than one\n"
|
||||
" of these options.\n")
|
||||
(princ (buffer-string))))
|
||||
|
||||
(defun *org-babel-tangle (orig-fn &rest args)
|
||||
"Don't write tangled blocks to files, print them to stdout."
|
||||
(cl-letf (((symbol-function 'write-region)
|
||||
(lambda (start end filename &optional append visit lockname mustbenew)
|
||||
(princ (buffer-string)))))
|
||||
(apply orig-fn args)))
|
||||
|
||||
(defun *org-babel-tangle-collect-blocks (&optional language tangle-file)
|
||||
"Like `org-babel-tangle-collect-blocks', but will ignore blocks that are in
|
||||
trees with the :notangle: tag."
|
||||
(let ((counter 0) last-heading-pos blocks)
|
||||
(org-babel-map-src-blocks (buffer-file-name)
|
||||
(let ((current-heading-pos
|
||||
(org-with-wide-buffer
|
||||
(org-with-limited-levels (outline-previous-heading)))))
|
||||
(if (eq last-heading-pos current-heading-pos) (cl-incf counter)
|
||||
(setq counter 1)
|
||||
(setq last-heading-pos current-heading-pos)))
|
||||
(unless (org-in-commented-heading-p)
|
||||
(require 'org)
|
||||
(let* ((tags (org-get-tags-at))
|
||||
(info (org-babel-get-src-block-info 'light))
|
||||
(src-lang (nth 0 info))
|
||||
(src-tfile (cdr (assq :tangle (nth 2 info)))))
|
||||
(cond ((member "notangle" tags))
|
||||
|
||||
((and (or or-tags and-tags)
|
||||
(or (not and-tags)
|
||||
(let ((a (cl-intersection and-tags tags :test #'string=))
|
||||
(b and-tags))
|
||||
(not (or (cl-set-difference a b :test #'equal)
|
||||
(cl-set-difference b a :test #'equal)))))
|
||||
(or (not or-tags)
|
||||
(cl-intersection or-tags tags :test #'string=))
|
||||
t))
|
||||
|
||||
((or (not (or all-blocks src-tfile))
|
||||
(string= src-tfile "no") ; tangle blocks by default
|
||||
(and tangle-file (not (equal tangle-file src-tfile)))
|
||||
(and language (not (string= language src-lang)))))
|
||||
|
||||
;; Add the spec for this block to blocks under its language.
|
||||
((let ((by-lang (assoc src-lang blocks))
|
||||
(block (org-babel-tangle-single-block counter)))
|
||||
(if by-lang
|
||||
(setcdr by-lang (cons block (cdr by-lang)))
|
||||
(push (cons src-lang (list block)) blocks))))))))
|
||||
;; Ensure blocks are in the correct order.
|
||||
(mapcar (lambda (b) (cons (car b) (nreverse (cdr b)))) blocks)))
|
||||
(advice-add #'org-babel-tangle-collect-blocks
|
||||
:override #'*org-babel-tangle-collect-blocks)
|
||||
|
||||
(defvar all-blocks nil)
|
||||
(defvar and-tags nil)
|
||||
(defvar or-tags nil)
|
||||
(let (lang srcs and-tags or-tags)
|
||||
(pop argv)
|
||||
(while argv
|
||||
(let ((arg (pop argv)))
|
||||
(pcase arg
|
||||
((or "-h" "--help")
|
||||
(usage)
|
||||
(error ""))
|
||||
((or "-a" "--all")
|
||||
(setq all-blocks t))
|
||||
((or "-l" "--lang")
|
||||
(setq lang (pop argv)))
|
||||
((or "-p" "--print")
|
||||
(advice-add #'org-babel-tangle :around #'*org-babel-tangle))
|
||||
((or "-t" "--tag" "--and")
|
||||
(push (pop argv) and-tags))
|
||||
("--or"
|
||||
(push (pop argv) or-tags))
|
||||
((guard (string-match-p "^--lang=" arg))
|
||||
(setq lang (cadr (split-string arg "=" t t))))
|
||||
((guard (file-directory-p arg))
|
||||
(setq srcs
|
||||
(append (directory-files-recursively arg "\\.org$")
|
||||
srcs)))
|
||||
((guard (file-exists-p arg))
|
||||
(push arg srcs))
|
||||
(_ (error "Unknown option or file: %s" arg)))))
|
||||
|
||||
(dolist (file srcs)
|
||||
(let ((backup (make-temp-file (file-name-base file) nil ".backup.org")))
|
||||
(unwind-protect
|
||||
;; Prevent slow hooks from interfering
|
||||
(let (org-mode-hook)
|
||||
;; We do the ol' switcheroo because `org-babel-tangle' writes
|
||||
;; changes to the current file, which would be imposing on the user.
|
||||
(copy-file file backup t)
|
||||
(with-current-buffer (find-file-noselect file)
|
||||
;; Tangling doesn't expand #+INCLUDE directives, so we do it
|
||||
;; ourselves, since includes are so useful for literate configs!
|
||||
(org-export-expand-include-keyword)
|
||||
(org-babel-tangle nil nil lang)))
|
||||
(ignore-errors (copy-file backup file t))
|
||||
(ignore-errors (delete-file backup)))))
|
||||
(kill-emacs 0))
|
|
@ -0,0 +1,391 @@
|
|||
;;; core/autoload/buffers.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autoload
|
||||
(defvar doom-real-buffer-functions
|
||||
'(doom-dired-buffer-p)
|
||||
"A list of predicate functions run to determine if a buffer is real, unlike
|
||||
`doom-unreal-buffer-functions'. They are passed one argument: the buffer to be
|
||||
tested.
|
||||
|
||||
Should any of its function returns non-nil, the rest of the functions are
|
||||
ignored and the buffer is considered real.
|
||||
|
||||
See `doom-real-buffer-p' for more information.")
|
||||
|
||||
;;;###autoload
|
||||
(defvar doom-unreal-buffer-functions
|
||||
'(minibufferp doom-special-buffer-p doom-non-file-visiting-buffer-p)
|
||||
"A list of predicate functions run to determine if a buffer is *not* real,
|
||||
unlike `doom-real-buffer-functions'. They are passed one argument: the buffer to
|
||||
be tested.
|
||||
|
||||
Should any of these functions return non-nil, the rest of the functions are
|
||||
ignored and the buffer is considered unreal.
|
||||
|
||||
See `doom-real-buffer-p' for more information.")
|
||||
|
||||
;;;###autoload
|
||||
(defvar-local doom-real-buffer-p nil
|
||||
"If non-nil, this buffer should be considered real no matter what. See
|
||||
`doom-real-buffer-p' for more information.")
|
||||
|
||||
;;;###autoload
|
||||
(defvar doom-fallback-buffer-name "*scratch*"
|
||||
"The name of the buffer to fall back to if no other buffers exist (will create
|
||||
it if it doesn't exist).")
|
||||
|
||||
|
||||
;;
|
||||
;;; Functions
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-buffer-frame-predicate (buf)
|
||||
"To be used as the default frame buffer-predicate parameter. Returns nil if
|
||||
BUF should be skipped over by functions like `next-buffer' and `other-buffer'."
|
||||
(or (doom-real-buffer-p buf)
|
||||
(eq buf (doom-fallback-buffer))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-fallback-buffer ()
|
||||
"Returns the fallback buffer, creating it if necessary. By default this is the
|
||||
scratch buffer. See `doom-fallback-buffer-name' to change this."
|
||||
(let (buffer-list-update-hook)
|
||||
(get-buffer-create doom-fallback-buffer-name)))
|
||||
|
||||
;;;###autoload
|
||||
(defalias 'doom-buffer-list #'buffer-list)
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-project-buffer-list (&optional project)
|
||||
"Return a list of buffers belonging to the specified PROJECT.
|
||||
|
||||
If PROJECT is nil, default to the current project.
|
||||
|
||||
If no project is active, return all buffers."
|
||||
(let ((buffers (doom-buffer-list)))
|
||||
(if-let* ((project-root
|
||||
(if project (expand-file-name project)
|
||||
(doom-project-root))))
|
||||
(cl-loop for buf in buffers
|
||||
if (projectile-project-buffer-p buf project-root)
|
||||
collect buf)
|
||||
buffers)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-open-projects ()
|
||||
"Return a list of projects with open buffers."
|
||||
(cl-loop with projects = (make-hash-table :test 'equal :size 8)
|
||||
for buffer in (doom-buffer-list)
|
||||
if (buffer-live-p buffer)
|
||||
if (doom-real-buffer-p buffer)
|
||||
if (with-current-buffer buffer (doom-project-root))
|
||||
do (puthash (abbreviate-file-name it) t projects)
|
||||
finally return (hash-table-keys projects)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-dired-buffer-p (buf)
|
||||
"Returns non-nil if BUF is a dired buffer."
|
||||
(with-current-buffer buf (derived-mode-p 'dired-mode)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-special-buffer-p (buf)
|
||||
"Returns non-nil if BUF's name starts and ends with an *."
|
||||
(equal (substring (buffer-name buf) 0 1) "*"))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-temp-buffer-p (buf)
|
||||
"Returns non-nil if BUF is temporary."
|
||||
(equal (substring (buffer-name buf) 0 1) " "))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-visible-buffer-p (buf)
|
||||
"Return non-nil if BUF is visible."
|
||||
(get-buffer-window buf))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-buried-buffer-p (buf)
|
||||
"Return non-nil if BUF is not visible."
|
||||
(not (doom-visible-buffer-p buf)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-non-file-visiting-buffer-p (buf)
|
||||
"Returns non-nil if BUF does not have a value for `buffer-file-name'."
|
||||
(not (buffer-file-name buf)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-real-buffer-list (&optional buffer-list)
|
||||
"Return a list of buffers that satify `doom-real-buffer-p'."
|
||||
(cl-remove-if-not #'doom-real-buffer-p (or buffer-list (doom-buffer-list))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-real-buffer-p (buffer-or-name)
|
||||
"Returns t if BUFFER-OR-NAME is a 'real' buffer.
|
||||
|
||||
A real buffer is a useful buffer; a first class citizen in Doom. Real ones
|
||||
should get special treatment, because we will be spending most of our time in
|
||||
them. Unreal ones should be low-profile and easy to cast aside, so we can focus
|
||||
on real ones.
|
||||
|
||||
The exact criteria for a real buffer is:
|
||||
|
||||
1. A non-nil value for the buffer-local value of the `doom-real-buffer-p'
|
||||
variable OR
|
||||
2. Any function in `doom-real-buffer-functions' returns non-nil OR
|
||||
3. None of the functions in `doom-unreal-buffer-functions' must return
|
||||
non-nil.
|
||||
|
||||
If BUFFER-OR-NAME is omitted or nil, the current buffer is tested."
|
||||
(or (bufferp buffer-or-name)
|
||||
(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)
|
||||
(run-hook-with-args-until-success 'doom-real-buffer-functions buf)
|
||||
(not (run-hook-with-args-until-success 'doom-unreal-buffer-functions buf))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-unreal-buffer-p (buffer-or-name)
|
||||
"Return t if BUFFER-OR-NAME is an 'unreal' buffer.
|
||||
|
||||
See `doom-real-buffer-p' for details on what that means."
|
||||
(not (doom-real-buffer-p buffer-or-name)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-buffers-in-mode (modes &optional buffer-list derived-p)
|
||||
"Return a list of buffers whose `major-mode' is `eq' to MODE(S).
|
||||
|
||||
If DERIVED-P, test with `derived-mode-p', otherwise use `eq'."
|
||||
(let ((modes (doom-enlist modes)))
|
||||
(cl-remove-if-not (if derived-p
|
||||
(lambda (buf)
|
||||
(with-current-buffer buf
|
||||
(apply #'derived-mode-p modes)))
|
||||
(lambda (buf)
|
||||
(memq (buffer-local-value 'major-mode buf) modes)))
|
||||
(or buffer-list (doom-buffer-list)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-visible-windows (&optional window-list)
|
||||
"Return a list of the visible, non-popup (dedicated) windows."
|
||||
(cl-loop for window in (or window-list (window-list))
|
||||
when (or (window-parameter window 'visible)
|
||||
(not (window-dedicated-p window)))
|
||||
collect window))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-visible-buffers (&optional buffer-list)
|
||||
"Return a list of visible buffers (i.e. not buried)."
|
||||
(let ((buffers (delete-dups (mapcar #'window-buffer (window-list)))))
|
||||
(if buffer-list
|
||||
(cl-delete-if (lambda (b) (memq b buffer-list))
|
||||
buffers)
|
||||
(delete-dups buffers))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-buried-buffers (&optional buffer-list)
|
||||
"Get a list of buffers that are buried."
|
||||
(cl-remove-if #'get-buffer-window (or buffer-list (doom-buffer-list))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-matching-buffers (pattern &optional buffer-list)
|
||||
"Get a list of all buffers that match the regex PATTERN."
|
||||
(cl-loop for buf in (or buffer-list (doom-buffer-list))
|
||||
when (string-match-p pattern (buffer-name buf))
|
||||
collect buf))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-set-buffer-real (buffer flag)
|
||||
"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)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-kill-buffer-and-windows (buffer)
|
||||
"Kill the buffer and delete all the windows it's displayed in."
|
||||
(dolist (window (get-buffer-window-list buffer))
|
||||
(unless (one-window-p t)
|
||||
(delete-window window)))
|
||||
(kill-buffer buffer))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-fixup-windows (windows)
|
||||
"Ensure that each of WINDOWS is showing a real buffer or the fallback buffer."
|
||||
(dolist (window windows)
|
||||
(with-selected-window window
|
||||
(when (doom-unreal-buffer-p (window-buffer))
|
||||
(previous-buffer)
|
||||
(when (doom-unreal-buffer-p (window-buffer))
|
||||
(switch-to-buffer (doom-fallback-buffer)))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-kill-buffer-fixup-windows (buffer)
|
||||
"Kill the BUFFER and ensure all the windows it was displayed in have switched
|
||||
to a real buffer or the fallback buffer."
|
||||
(let ((windows (get-buffer-window-list buffer)))
|
||||
(kill-buffer buffer)
|
||||
(doom-fixup-windows (cl-remove-if-not #'window-live-p windows))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-kill-buffers-fixup-windows (buffers)
|
||||
"Kill the BUFFERS and ensure all the windows they were displayed in have
|
||||
switched to a real buffer or the fallback buffer."
|
||||
(let ((seen-windows (make-hash-table :test 'eq :size 8)))
|
||||
(dolist (buffer buffers)
|
||||
(let ((windows (get-buffer-window-list buffer)))
|
||||
(kill-buffer buffer)
|
||||
(dolist (window (cl-remove-if-not #'window-live-p windows))
|
||||
(puthash window t seen-windows))))
|
||||
(doom-fixup-windows (hash-table-keys seen-windows))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-kill-matching-buffers (pattern &optional buffer-list)
|
||||
"Kill all buffers (in current workspace OR in BUFFER-LIST) that match the
|
||||
regex PATTERN. Returns the number of killed buffers."
|
||||
(let ((buffers (doom-matching-buffers pattern buffer-list)))
|
||||
(dolist (buf buffers (length buffers))
|
||||
(kill-buffer buf))))
|
||||
|
||||
|
||||
;;
|
||||
;; Hooks
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-mark-buffer-as-real-h ()
|
||||
"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))
|
||||
|
||||
|
||||
;;
|
||||
;; Interactive commands
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/save-and-kill-buffer ()
|
||||
"Save the current buffer to file, then kill it."
|
||||
(interactive)
|
||||
(save-buffer)
|
||||
(kill-current-buffer))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/kill-this-buffer-in-all-windows (buffer &optional dont-save)
|
||||
"Kill BUFFER globally and ensure all windows previously showing this buffer
|
||||
have switched to a real buffer or the fallback buffer.
|
||||
|
||||
If DONT-SAVE, don't prompt to save modified buffers (discarding their changes)."
|
||||
(interactive
|
||||
(list (current-buffer) current-prefix-arg))
|
||||
(cl-assert (bufferp buffer) t)
|
||||
(when (and (buffer-modified-p buffer) dont-save)
|
||||
(with-current-buffer buffer
|
||||
(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.
|
||||
|
||||
If the prefix arg is passed, doesn't close windows and only kill buffers that
|
||||
belong to the current project."
|
||||
(interactive
|
||||
(list (if current-prefix-arg
|
||||
(doom-project-buffer-list)
|
||||
(doom-buffer-list))
|
||||
t))
|
||||
(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 #'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)
|
||||
"Kill all other buffers (besides the current one).
|
||||
|
||||
If the prefix arg is passed, kill only buffers that belong to the current
|
||||
project."
|
||||
(interactive
|
||||
(list (delq (current-buffer)
|
||||
(if current-prefix-arg
|
||||
(doom-project-buffer-list)
|
||||
(doom-buffer-list)))
|
||||
t))
|
||||
(mapc #'doom-kill-buffer-and-windows 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)
|
||||
"Kill buffers that match PATTERN in BUFFER-LIST.
|
||||
|
||||
If the prefix arg is passed, only kill matching buffers in the current project."
|
||||
(interactive
|
||||
(list (read-regexp "Buffer pattern: ")
|
||||
(if current-prefix-arg
|
||||
(doom-project-buffer-list)
|
||||
(doom-buffer-list))
|
||||
t))
|
||||
(doom-kill-matching-buffers pattern buffer-list)
|
||||
(when interactive
|
||||
(message "Killed %d buffer(s)"
|
||||
(- (length buffer-list)
|
||||
(length (cl-remove-if-not #'buffer-live-p buffer-list))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/kill-buried-buffers (&optional buffer-list interactive)
|
||||
"Kill buffers that are buried.
|
||||
|
||||
If PROJECT-P (universal argument), only kill buried buffers belonging to the
|
||||
current project."
|
||||
(interactive
|
||||
(list (doom-buried-buffers
|
||||
(if current-prefix-arg (doom-project-buffer-list)))
|
||||
t))
|
||||
(mapc #'kill-buffer 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)
|
||||
"Kill buffers for the specified PROJECT."
|
||||
(interactive
|
||||
(list (if-let (open-projects (doom-open-projects))
|
||||
(completing-read
|
||||
"Kill buffers for project: " open-projects
|
||||
nil t nil nil
|
||||
(if-let* ((project-root (doom-project-root))
|
||||
(project-root (abbreviate-file-name project-root))
|
||||
((member project-root open-projects)))
|
||||
project-root))
|
||||
(message "No projects are open!")
|
||||
nil)
|
||||
t))
|
||||
(when project
|
||||
(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)))))))
|
|
@ -0,0 +1,142 @@
|
|||
;;; core/autoload/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-bin-dir (concat doom-emacs-dir "bin/"))
|
||||
(defvar doom-bin (concat doom-bin-dir "doom"))
|
||||
|
||||
;;;###autoload
|
||||
(defvar doom-reloading-p nil
|
||||
"TODO")
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/open-private-config ()
|
||||
"Browse your `doom-private-dir'."
|
||||
(interactive)
|
||||
(unless (file-directory-p doom-private-dir)
|
||||
(make-directory doom-private-dir t))
|
||||
(doom-project-browse doom-private-dir))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/find-file-in-private-config ()
|
||||
"Search for a file in `doom-private-dir'."
|
||||
(interactive)
|
||||
(doom-project-find-file doom-private-dir))
|
||||
|
||||
;;;###autoload
|
||||
(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
|
||||
(or (save-excursion
|
||||
(goto-char (point-min))
|
||||
(search-forward "(doom!" nil t))
|
||||
(point))))
|
||||
|
||||
;;;###autoload
|
||||
(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-private-packages-file ()
|
||||
"Open your private packages.el file."
|
||||
(interactive)
|
||||
(find-file (expand-file-name "packages.el" doom-private-dir)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Managements
|
||||
|
||||
(defmacro doom--if-compile (command on-success &optional on-failure)
|
||||
(declare (indent 2))
|
||||
`(with-current-buffer (compile ,command t)
|
||||
(let ((w (get-buffer-window (current-buffer))))
|
||||
(select-window w)
|
||||
(add-hook
|
||||
'compilation-finish-functions
|
||||
(lambda (_buf status)
|
||||
(if (equal status "finished\n")
|
||||
(progn
|
||||
(delete-window w)
|
||||
,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 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-after-reload-hook' afterwards."
|
||||
(interactive)
|
||||
(require 'core-cli)
|
||||
(when (and IS-WINDOWS (file-exists-p doom-env-file))
|
||||
(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))
|
||||
(run-hook-wrapped 'doom-before-reload-hook #'doom-try-run-hook)
|
||||
(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-after-reload-hook #'doom-try-run-hook)
|
||||
(message "Config successfully reloaded!"))
|
||||
(user-error "Failed to reload your config")))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/reload-autoloads ()
|
||||
"Reload only `doom-autoloads-file' and `doom-package-autoload-file'.
|
||||
|
||||
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 sync' on the command
|
||||
line."
|
||||
(interactive)
|
||||
(require 'core-cli)
|
||||
(require 'core-packages)
|
||||
(doom-initialize-packages)
|
||||
(doom-autoloads-reload))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/reload-env (&optional arg)
|
||||
"Regenerates and/or reloads your envvar file.
|
||||
|
||||
If passed the prefix ARG, clear the envvar file. Uses the same mechanism as
|
||||
'bin/doom env'.
|
||||
|
||||
An envvar file contains a snapshot of your shell environment, which can be
|
||||
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--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--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))))
|
|
@ -0,0 +1,497 @@
|
|||
;;; core/autoload/debug.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Doom's debug mode
|
||||
|
||||
;;;###autoload
|
||||
(defvar doom-debug-variables
|
||||
'(debug-on-error
|
||||
doom-debug-p
|
||||
garbage-collection-messages
|
||||
gcmh-verbose
|
||||
init-file-debug
|
||||
jka-compr-verbose
|
||||
url-debug
|
||||
use-package-verbose
|
||||
(message-log-max . 16384))
|
||||
"A list of variable to toggle on `doom-debug-mode'.
|
||||
|
||||
Each entry can be a variable symbol or a cons cell whose CAR is the variable
|
||||
symbol and CDR is the value to set it to when `doom-debug-mode' is activated.")
|
||||
|
||||
(defvar doom--debug-vars-old-values nil)
|
||||
(defvar doom--debug-vars-undefined nil)
|
||||
|
||||
(defun doom--watch-debug-vars-h (&rest _)
|
||||
(when-let (bound-vars (cl-remove-if-not #'boundp doom--debug-vars-undefined))
|
||||
(doom-log "New variables available: %s" bound-vars)
|
||||
(let ((message-log-max nil))
|
||||
(doom-debug-mode -1)
|
||||
(doom-debug-mode +1))))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode doom-debug-mode
|
||||
"Toggle `debug-on-error' and `doom-debug-p' for verbose logging."
|
||||
:init-value nil
|
||||
:global t
|
||||
(let ((enabled doom-debug-mode))
|
||||
(setq doom--debug-vars-undefined nil)
|
||||
(dolist (var doom-debug-variables)
|
||||
(cond ((listp var)
|
||||
(cl-destructuring-bind (var . val) var
|
||||
(if (not (boundp var))
|
||||
(add-to-list 'doom--debug-vars-undefined var)
|
||||
(set-default
|
||||
var (if (not enabled)
|
||||
(alist-get var doom--debug-vars-old-values)
|
||||
(setf (alist-get var doom--debug-vars-old-values)
|
||||
(symbol-value var))
|
||||
val)))))
|
||||
((if (boundp var)
|
||||
(set-default var enabled)
|
||||
(add-to-list 'doom--debug-vars-undefined var)))))
|
||||
(when (called-interactively-p 'any)
|
||||
(when (fboundp 'explain-pause-mode)
|
||||
(explain-pause-mode (if enabled +1 -1))))
|
||||
;; Watch for changes in `doom-debug-variables', or when packages load (and
|
||||
;; potentially define one of `doom-debug-variables'), in case some of them
|
||||
;; aren't defined when `doom-debug-mode' is first loaded.
|
||||
(cond (enabled
|
||||
(add-variable-watcher 'doom-debug-variables #'doom--watch-debug-vars-h)
|
||||
(add-hook 'after-load-functions #'doom--watch-debug-vars-h))
|
||||
(t
|
||||
(remove-variable-watcher 'doom-debug-variables #'doom--watch-debug-vars-h)
|
||||
(remove-hook 'after-load-functions #'doom--watch-debug-vars-h)))
|
||||
(message "Debug mode %s" (if enabled "on" "off"))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Hooks
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-run-all-startup-hooks-h ()
|
||||
"Run all startup Emacs hooks. Meant to be executed after starting Emacs with
|
||||
-q or -Q, for example:
|
||||
|
||||
emacs -Q -l init.el -f doom-run-all-startup-hooks-h"
|
||||
(setq after-init-time (current-time))
|
||||
(let ((inhibit-startup-hooks nil))
|
||||
(mapc (lambda (hook)
|
||||
(run-hook-wrapped hook #'doom-try-run-hook))
|
||||
'(after-init-hook
|
||||
delayed-warnings-hook
|
||||
emacs-startup-hook
|
||||
tty-setup-hook
|
||||
window-setup-hook))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(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))
|
||||
(letf! ((defun sh (&rest args) (cdr (apply #'doom-call-process args)))
|
||||
(defun abbrev-path (path)
|
||||
(replace-regexp-in-string
|
||||
(concat "\\<" (regexp-quote (user-login-name)) "\\>") "$USER"
|
||||
(abbreviate-file-name path))))
|
||||
`((system
|
||||
(type . ,system-type)
|
||||
(config . ,system-configuration)
|
||||
(shell . ,(abbrev-path shell-file-name))
|
||||
(uname . ,(if IS-WINDOWS "n/a" (sh "uname" "-msrv")))
|
||||
(path . ,(mapcar #'abbrev-path exec-path)))
|
||||
(emacs
|
||||
(dir . ,(abbrev-path (file-truename doom-emacs-dir)))
|
||||
(version . ,emacs-version)
|
||||
(build . ,(format-time-string "%b %d, %Y" emacs-build-time))
|
||||
(buildopts . ,system-configuration-options)
|
||||
(features . ,system-configuration-features)
|
||||
(traits . ,(delq
|
||||
nil (list (cond ((not doom-interactive-p) 'batch)
|
||||
((display-graphic-p) 'gui)
|
||||
('tty))
|
||||
(if (daemonp) 'daemon)
|
||||
(if (and (require 'server)
|
||||
(server-running-p))
|
||||
'server-running)
|
||||
(if (boundp 'chemacs-profiles-path)
|
||||
'chemacs)
|
||||
(if (file-exists-p doom-env-file)
|
||||
'envvar-file)
|
||||
(if (featurep 'exec-path-from-shell)
|
||||
'exec-path-from-shell)
|
||||
(if (file-symlink-p user-emacs-directory)
|
||||
'symlinked-emacsdir)
|
||||
(if (file-symlink-p doom-private-dir)
|
||||
'symlinked-doomdir)))))
|
||||
(doom
|
||||
(dir . ,(abbrev-path (file-truename doom-private-dir)))
|
||||
(version . ,doom-version)
|
||||
,@(when doom-interactive-p
|
||||
`((font . ,(bound-and-true-p doom-font))
|
||||
(theme . ,(bound-and-true-p doom-theme))))
|
||||
(build . ,(sh "git" "log" "-1" "--format=%D %h %ci"))
|
||||
(elc-files
|
||||
. ,(length (doom-files-in `(,@doom-modules-dirs
|
||||
,doom-core-dir
|
||||
,doom-private-dir)
|
||||
:type 'files :match "\\.elc$")))
|
||||
(modules
|
||||
,@(or (cl-loop with cat = nil
|
||||
for key being the hash-keys of doom-modules
|
||||
if (or (not cat)
|
||||
(not (eq cat (car key))))
|
||||
do (setq cat (car key))
|
||||
and collect cat
|
||||
collect
|
||||
(let* ((flags (doom-module-get cat (cdr key) :flags))
|
||||
(path (doom-module-get cat (cdr key) :path))
|
||||
(module (append (cond ((null path)
|
||||
(list '&nopath))
|
||||
((file-in-directory-p path doom-private-dir)
|
||||
(list '&user)))
|
||||
(if flags
|
||||
`(,(cdr key) ,@flags)
|
||||
(list (cdr key))))))
|
||||
(if (= (length module) 1)
|
||||
(car module)
|
||||
module)))
|
||||
'("n/a")))
|
||||
(packages
|
||||
,@(or (condition-case e
|
||||
(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
|
||||
,@(or (condition-case e
|
||||
(progn
|
||||
(package-initialize)
|
||||
(cl-loop for (name . _) in package-alist
|
||||
collect (format "%s" name)))
|
||||
(error (format "<%S>" e)))
|
||||
'("n/a"))))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/version ()
|
||||
"Display the current version of Doom & Emacs, including the current Doom
|
||||
branch and commit."
|
||||
(interactive)
|
||||
(let ((default-directory doom-core-dir))
|
||||
(print! "Doom v%s (%s)"
|
||||
doom-version
|
||||
(or (cdr (doom-call-process "git" "log" "-1" "--format=%D %h %ci"))
|
||||
"n/a"))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/info (&optional raw)
|
||||
"Collects some debug information about your Emacs session, formats it and
|
||||
copies it to your clipboard, ready to be pasted into bug reports!"
|
||||
(interactive "P")
|
||||
(let ((buffer (get-buffer-create "*doom info*"))
|
||||
(info (doom-info)))
|
||||
(with-current-buffer buffer
|
||||
(erase-buffer)
|
||||
(if raw
|
||||
(progn
|
||||
(save-excursion
|
||||
(pp info (current-buffer)))
|
||||
(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 "```\n")
|
||||
(dolist (group info)
|
||||
(insert! "%-8s%-10s %s\n"
|
||||
((upcase (symbol-name (car group)))
|
||||
(caadr group)
|
||||
(cdadr group)))
|
||||
(dolist (spec (cddr group))
|
||||
(insert! (indent 8 "%-10s %s\n")
|
||||
((car spec) (cdr spec)))))
|
||||
(insert "```\n"))
|
||||
(if (not doom-interactive-p)
|
||||
(print! (buffer-string))
|
||||
(pop-to-buffer buffer)
|
||||
(kill-new (buffer-string))
|
||||
(print! (green "Copied your doom info to clipboard"))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/am-i-secure ()
|
||||
"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://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))
|
||||
collect bad)))
|
||||
(error "tls seems to be misconfigured (it got %s)."
|
||||
bad-hosts)
|
||||
(url-retrieve "https://badssl.com"
|
||||
(lambda (status)
|
||||
(if (or (not status) (plist-member status :error))
|
||||
(warn "Something went wrong.\n\n%s" (pp-to-string status))
|
||||
(message "Your trust roots are set up properly.\n\n%s" (pp-to-string status))
|
||||
t)))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Vanilla sandbox
|
||||
|
||||
(defun doom--run-sandbox (&optional mode)
|
||||
(interactive)
|
||||
(let ((contents (buffer-string))
|
||||
(file (make-temp-file "doom-sandbox-")))
|
||||
(require 'package)
|
||||
(with-temp-file file
|
||||
(prin1 `(progn
|
||||
(setq before-init-time (current-time)
|
||||
after-init-time nil
|
||||
noninteractive nil
|
||||
user-init-file ,file
|
||||
process-environment ',doom--initial-process-environment
|
||||
exec-path ',doom--initial-exec-path
|
||||
doom-debug-p t
|
||||
init-file-debug t
|
||||
doom--initial-load-path load-path
|
||||
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
|
||||
comp-deferred-compilation nil
|
||||
comp-eln-load-path ',(bound-and-true-p comp-eln-load-path)
|
||||
comp-async-env-modifier-form ',(bound-and-true-p comp-async-env-modifier-form)
|
||||
comp-deferred-compilation-black-list ',(bound-and-true-p comp-deferred-compilation-black-list))
|
||||
(with-eval-after-load 'undo-tree
|
||||
;; HACK `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)))
|
||||
(`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--)))
|
||||
(`vanilla ; nothing loaded
|
||||
`(progn
|
||||
(package-initialize)
|
||||
(--run--))))
|
||||
(current-buffer))
|
||||
;; Redo all startup initialization, like running startup hooks and loading
|
||||
;; init files.
|
||||
(prin1 `(progn
|
||||
(fset 'doom-try-run-hook #',(symbol-function #'doom-try-run-hook))
|
||||
(fset 'doom-run-all-startup-hooks-h #',(symbol-function #'doom-run-all-startup-hooks-h))
|
||||
(doom-run-all-startup-hooks-h))
|
||||
(current-buffer)))
|
||||
(let ((args (if (eq mode 'doom)
|
||||
(list "-l" file)
|
||||
(list "-Q" "-l" file))))
|
||||
(require 'restart-emacs)
|
||||
(condition-case e
|
||||
(cond ((display-graphic-p)
|
||||
(if (memq system-type '(windows-nt ms-dos))
|
||||
(restart-emacs--start-gui-on-windows args)
|
||||
(restart-emacs--start-gui-using-sh args)))
|
||||
((memq system-type '(windows-nt ms-dos))
|
||||
(user-error "Cannot start another Emacs from Windows shell."))
|
||||
((suspend-emacs
|
||||
(format "%s %s -nw; fg"
|
||||
(shell-quote-argument (restart-emacs--get-emacs-binary))
|
||||
(mapconcat #'shell-quote-argument args " ")))))
|
||||
(error
|
||||
(delete-file file)
|
||||
(signal (car e) (cdr e)))))))
|
||||
|
||||
(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)))
|
||||
(define-key map (kbd "C-c C-c") #'doom--run-vanilla-emacs)
|
||||
(define-key map (kbd "C-c C-d") #'doom--run-vanilla-doom)
|
||||
(define-key map (kbd "C-c C-p") #'doom--run-vanilla-doom+)
|
||||
(define-key map (kbd "C-c C-f") #'doom--run-full-doom)
|
||||
(define-key map (kbd "C-c C-k") #'kill-current-buffer)
|
||||
map))
|
||||
|
||||
(define-derived-mode doom-sandbox-emacs-lisp-mode emacs-lisp-mode "Sandbox Elisp"
|
||||
"TODO")
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/sandbox ()
|
||||
"Open the Emacs Lisp sandbox.
|
||||
|
||||
This is a test bed for running Emacs Lisp in another instance of Emacs with
|
||||
varying amounts of Doom loaded, including:
|
||||
|
||||
a) vanilla Emacs (nothing loaded),
|
||||
b) vanilla Doom (only Doom core),
|
||||
c) Doom + modules - your private config or
|
||||
c) Doom + modules + your private config (a complete Doom session)
|
||||
|
||||
This is done without sacrificing access to installed packages. Use the sandbox
|
||||
to reproduce bugs and determine if Doom is to blame."
|
||||
(interactive)
|
||||
(let* ((buffer-name "*doom:sandbox*")
|
||||
(exists (get-buffer buffer-name))
|
||||
(buf (get-buffer-create buffer-name)))
|
||||
(with-current-buffer buf
|
||||
(doom-sandbox-emacs-lisp-mode)
|
||||
(setq-local default-directory doom-emacs-dir)
|
||||
(unless (buffer-live-p exists)
|
||||
(insert-file-contents (doom-glob doom-core-dir "templates/VANILLA_SANDBOX"))
|
||||
(let ((contents (substitute-command-keys (buffer-string))))
|
||||
(erase-buffer)
|
||||
(insert contents "\n")))
|
||||
(goto-char (point-max)))
|
||||
(pop-to-buffer buf)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Reporting bugs
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/report-bug ()
|
||||
"Open a markdown buffer destinated to populate the New Issue page on Doom
|
||||
Emacs' issue tracker.
|
||||
|
||||
If called when a backtrace buffer is present, it and the output of `doom-info'
|
||||
will be automatically appended to the result."
|
||||
(interactive)
|
||||
(browse-url "https://github.com/hlissner/doom-emacs/issues/new/choose"))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/copy-buffer-contents (buffer-name)
|
||||
"Copy the contents of BUFFER-NAME to your clipboard."
|
||||
(interactive
|
||||
(list (if current-prefix-arg
|
||||
(completing-read "Select buffer: " (mapcar #'buffer-name (buffer-list)))
|
||||
(buffer-name (current-buffer)))))
|
||||
(let ((buffer (get-buffer buffer-name)))
|
||||
(unless (buffer-live-p buffer)
|
||||
(user-error "Buffer isn't live"))
|
||||
(kill-new
|
||||
(with-current-buffer buffer
|
||||
(substring-no-properties (buffer-string))))
|
||||
(message "Contents of %S were copied to the clipboard" buffer-name)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Profiling
|
||||
|
||||
(defvar doom--profiler nil)
|
||||
;;;###autoload
|
||||
(defun doom/toggle-profiler ()
|
||||
"Toggle the Emacs profiler. Run it again to see the profiling report."
|
||||
(interactive)
|
||||
(if (not doom--profiler)
|
||||
(profiler-start 'cpu+mem)
|
||||
(profiler-report)
|
||||
(profiler-stop))
|
||||
(setq doom--profiler (not doom--profiler)))
|
|
@ -0,0 +1,339 @@
|
|||
;;; core/autoload/files.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defun doom--resolve-path-forms (spec &optional directory)
|
||||
"Converts a simple nested series of or/and forms into a series of
|
||||
`file-exists-p' checks.
|
||||
|
||||
For example
|
||||
|
||||
(doom--resolve-path-forms
|
||||
'(or A (and B C))
|
||||
\"~\")
|
||||
|
||||
Returns (approximately):
|
||||
|
||||
'(let* ((_directory \"~\")
|
||||
(A (expand-file-name A _directory))
|
||||
(B (expand-file-name B _directory))
|
||||
(C (expand-file-name C _directory)))
|
||||
(or (and (file-exists-p A) A)
|
||||
(and (if (file-exists-p B) B)
|
||||
(if (file-exists-p C) C))))
|
||||
|
||||
This is used by `file-exists-p!' and `project-file-exists-p!'."
|
||||
(declare (pure t) (side-effect-free t))
|
||||
(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 ((,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 ((segments (delq nil segments))
|
||||
dir)
|
||||
(while segments
|
||||
(setq dir (expand-file-name (car segments) dir)
|
||||
segments (cdr segments)))
|
||||
dir))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-glob (&rest segments)
|
||||
"Construct a path from SEGMENTS and expand glob patterns.
|
||||
Returns nil if the path doesn't exist.
|
||||
Ignores `nil' elements in SEGMENTS."
|
||||
(let* (case-fold-search
|
||||
(dir (apply #'doom--path segments)))
|
||||
(if (string-match-p "[[*?]" dir)
|
||||
(file-expand-wildcards dir t)
|
||||
(if (file-exists-p dir)
|
||||
dir))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-path (&rest segments)
|
||||
"Constructs a file path from SEGMENTS.
|
||||
Ignores `nil' elements in SEGMENTS."
|
||||
(if segments
|
||||
(apply #'doom--path segments)
|
||||
(file!)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-dir (&rest segments)
|
||||
"Constructs a path from SEGMENTS.
|
||||
See `doom-path'.
|
||||
Ignores `nil' elements in SEGMENTS."
|
||||
(when-let (path (apply #'doom-path segments))
|
||||
(directory-file-name (file-name-directory path))))
|
||||
|
||||
;;;###autoload
|
||||
(cl-defun doom-files-in
|
||||
(paths &rest rest
|
||||
&key
|
||||
filter
|
||||
map
|
||||
(full t)
|
||||
(follow-symlinks t)
|
||||
(type 'files)
|
||||
(relative-to (unless full default-directory))
|
||||
(depth 99999)
|
||||
(mindepth 0)
|
||||
(match "/[^._][^/]+"))
|
||||
"Return a list of files/directories in PATHS (one string or a list of them).
|
||||
|
||||
FILTER is a function or symbol that takes one argument (the path). If it returns
|
||||
non-nil, the entry will be excluded.
|
||||
|
||||
MAP is a function or symbol which will be used to transform each entry in the
|
||||
results.
|
||||
|
||||
TYPE determines what kind of path will be included in the results. This can be t
|
||||
(files and folders), 'files or 'dirs.
|
||||
|
||||
By default, this function returns paths relative to PATH-OR-PATHS if it is a
|
||||
single path. If it a list of paths, this function returns absolute paths.
|
||||
Otherwise, by setting RELATIVE-TO to a path, the results will be transformed to
|
||||
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 (result)
|
||||
(dolist (file (mapcan (doom-rpartial #'doom-glob "*") (doom-enlist paths)))
|
||||
(cond ((file-directory-p file)
|
||||
(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)))
|
||||
(<= mindepth 0))
|
||||
(push (if relative-to
|
||||
(file-relative-name file relative-to)
|
||||
file)
|
||||
result))))
|
||||
result))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-file-cookie-p (file &optional cookie null-value)
|
||||
"Returns the evaluated result of FORM in a ;;;###COOKIE FORM at the top of
|
||||
FILE.
|
||||
|
||||
If COOKIE doesn't exist, or cookie isn't within the first 256 bytes of FILE,
|
||||
return NULL-VALUE."
|
||||
(unless (file-exists-p file)
|
||||
(signal 'file-missing file))
|
||||
(unless (file-readable-p file)
|
||||
(error "%S is unreadable" file))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file nil 0 256)
|
||||
(if (re-search-forward (format "^;;;###%s " (regexp-quote (or cookie "if")))
|
||||
nil t)
|
||||
(let ((load-file-name file))
|
||||
(eval (sexp-at-point) t))
|
||||
null-value)))
|
||||
|
||||
;;;###autoload
|
||||
(defmacro file-exists-p! (files &optional directory)
|
||||
"Returns non-nil if the FILES in DIRECTORY all exist.
|
||||
|
||||
DIRECTORY is a path; defaults to `default-directory'.
|
||||
|
||||
Returns the last file found to meet the rules set by FILES, which can be a
|
||||
single file or nested compound statement of `and' and `or' statements."
|
||||
`(let ((p ,(doom--resolve-path-forms files directory)))
|
||||
(and p (expand-file-name p ,directory))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-file-size (file &optional dir)
|
||||
"Returns the size of FILE (in DIR) in bytes."
|
||||
(let ((file (expand-file-name file dir)))
|
||||
(unless (file-exists-p file)
|
||||
(error "Couldn't find file %S" file))
|
||||
(unless (file-readable-p file)
|
||||
(error "File %S is unreadable; can't acquire its filesize"
|
||||
file))
|
||||
(nth 7 (file-attributes file))))
|
||||
|
||||
(defvar w32-get-true-file-attributes)
|
||||
;;;###autoload
|
||||
(defun doom-directory-size (dir)
|
||||
"Returns the size of FILE (in DIR) in kilobytes."
|
||||
(unless (file-directory-p dir)
|
||||
(error "Directory %S does not exist" dir))
|
||||
(if (executable-find "du")
|
||||
(/ (string-to-number (cdr (doom-call-process "du" "-sb" dir)))
|
||||
1024.0)
|
||||
;; REVIEW This is slow and terribly inaccurate, but it's something
|
||||
(let ((w32-get-true-file-attributes t)
|
||||
(file-name-handler-alist dir)
|
||||
(max-lisp-eval-depth 5000)
|
||||
(sum 0.0))
|
||||
(dolist (attrs (directory-files-and-attributes dir nil nil t) sum)
|
||||
(unless (member (car attrs) '("." ".."))
|
||||
(cl-incf
|
||||
sum (if (eq (nth 1 attrs) t) ; is directory
|
||||
(doom-directory-size (expand-file-name (car attrs) dir))
|
||||
(/ (nth 8 attrs) 1024.0))))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(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))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/delete-this-file (&optional path force-p)
|
||||
"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 (buffer-file-name (buffer-base-buffer))
|
||||
current-prefix-arg))
|
||||
(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) 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."
|
||||
(interactive
|
||||
(list (read-file-name "Copy file to: ")
|
||||
current-prefix-arg))
|
||||
(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."
|
||||
(interactive
|
||||
(list (read-file-name "Move file to: ")
|
||||
current-prefix-arg))
|
||||
(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-path (file)
|
||||
(let ((host (or (file-remote-p file 'host) "localhost")))
|
||||
(concat "/" (when (file-remote-p file)
|
||||
(concat (file-remote-p file 'method) ":"
|
||||
(if-let (user (file-remote-p file 'user))
|
||||
(concat user "@" host)
|
||||
host)
|
||||
"|"))
|
||||
"sudo:root@" host
|
||||
":" (or (file-remote-p file 'localname)
|
||||
file))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/sudo-find-file (file)
|
||||
"Open FILE as root."
|
||||
(interactive "FOpen file as root: ")
|
||||
(find-file (doom--sudo-file-path file)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/sudo-this-file ()
|
||||
"Open the current file as root."
|
||||
(interactive)
|
||||
(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))))
|
|
@ -0,0 +1,132 @@
|
|||
;;; core/autoload/fonts.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autoload
|
||||
(defvar doom-font-increment 2
|
||||
"How many steps to increase the font size each time `doom/increase-font-size'
|
||||
or `doom/decrease-font-size' are invoked.")
|
||||
|
||||
;;;###autoload
|
||||
(defvar doom-big-font nil
|
||||
"The font to use for `doom-big-font-mode'. If nil, `doom-font' will be used,
|
||||
scaled up by `doom-big-font-increment'. See `doom-font' for details on
|
||||
acceptable values for this variable.")
|
||||
|
||||
;;;###autoload
|
||||
(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.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Library
|
||||
|
||||
(defun doom--font-name (fontname)
|
||||
(when (query-fontset fontname)
|
||||
(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")))
|
||||
|
||||
(defvar doom--font-scale nil)
|
||||
;;;###autoload
|
||||
(defun doom-adjust-font-size (increment)
|
||||
"Increase size of font in FRAME by INCREMENT.
|
||||
FRAME parameter defaults to current frame."
|
||||
(if (null increment)
|
||||
(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))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/reload-font ()
|
||||
"Reload your fonts, if they're set.
|
||||
See `doom-init-fonts-h'."
|
||||
(interactive)
|
||||
(when doom-font
|
||||
(set-frame-font doom-font t))
|
||||
(doom-init-fonts-h)
|
||||
(mapc #'doom-init-extra-fonts-h (frame-list)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/increase-font-size (count)
|
||||
"Enlargens the font size across the current and child frames."
|
||||
(interactive "p")
|
||||
(doom-adjust-font-size count))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/decrease-font-size (count)
|
||||
"Shrinks the font size across the current and child frames."
|
||||
(interactive "p")
|
||||
(doom-adjust-font-size (- count)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/reset-font-size ()
|
||||
"Reset font size and `text-scale'.
|
||||
|
||||
Assuming it has been adjusted via `doom/increase-font-size' and
|
||||
`doom/decrease-font-size', or `text-scale-*' commands."
|
||||
(interactive)
|
||||
(let (success)
|
||||
(when (and (boundp 'text-scale-mode-amount)
|
||||
(/= text-scale-mode-amount 0))
|
||||
(text-scale-set 0)
|
||||
(setq success t))
|
||||
(when (doom-adjust-font-size nil)
|
||||
(setq success t))
|
||||
(unless success
|
||||
(user-error "The font hasn't been resized"))))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode doom-big-font-mode
|
||||
"A global mode that resizes the font, for streams, screen-sharing and
|
||||
presentations.
|
||||
|
||||
This uses `doom/increase-font-size' under the hood, and enlargens the font by
|
||||
`doom-big-font-increment'."
|
||||
:init-value nil
|
||||
:lighter " BIG"
|
||||
:global t
|
||||
(unless doom-font
|
||||
(user-error "`doom-font' must be set to a valid font"))
|
||||
(if doom-big-font
|
||||
(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)
|
||||
(/= doom-big-font-increment 0)
|
||||
doom-big-font-increment))))
|
|
@ -0,0 +1,737 @@
|
|||
;;; core/autoload/help.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom--help-major-mode-module-alist
|
||||
'((dockerfile-mode :tools docker)
|
||||
(agda2-mode :lang agda)
|
||||
(c-mode :lang cc)
|
||||
(c++-mode :lang cc)
|
||||
(objc++-mode :lang cc)
|
||||
(crystal-mode :lang crystal)
|
||||
(lisp-mode :lang common-lisp)
|
||||
(csharp-mode :lang csharp)
|
||||
(clojure-mode :lang clojure)
|
||||
(clojurescript-mode :lang clojure)
|
||||
(json-mode :lang json)
|
||||
(yaml-mode :lang yaml)
|
||||
(csv-mode :lang data)
|
||||
(erlang-mode :lang erlang)
|
||||
(elixir-mode :lang elixir)
|
||||
(elm-mode :lang elm)
|
||||
(emacs-lisp-mode :lang emacs-lisp)
|
||||
(ess-r-mode :lang ess)
|
||||
(ess-julia-mode :lang ess)
|
||||
(go-mode :lang go)
|
||||
(haskell-mode :lang haskell)
|
||||
(hy-mode :lang hy)
|
||||
(idris-mode :lang idris)
|
||||
(java-mode :lang java)
|
||||
(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)
|
||||
(latex-mode :lang latex)
|
||||
(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)
|
||||
(raku-mode :lang raku)
|
||||
(php-mode :lang php)
|
||||
(hack-mode :lang php)
|
||||
(plantuml-mode :lang plantuml)
|
||||
(purescript-mode :lang purescript)
|
||||
(python-mode :lang python)
|
||||
(restclient-mode :lang rest)
|
||||
(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)
|
||||
(css-mode :lang web)
|
||||
(scss-mode :lang web)
|
||||
(sass-mode :lang web)
|
||||
(less-css-mode :lang web)
|
||||
(stylus-mode :lang web)
|
||||
(terra-mode :lang terra))
|
||||
"An alist mapping major modes to Doom modules.
|
||||
|
||||
This is used by `doom/help-modules' to auto-select the module corresponding to
|
||||
the current major-modea.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-active-minor-modes ()
|
||||
"Return a list of active minor-mode symbols."
|
||||
(cl-loop for mode in minor-mode-list
|
||||
if (and (boundp mode) (symbol-value mode))
|
||||
collect mode))
|
||||
|
||||
|
||||
;;
|
||||
;;; Custom describe commands
|
||||
|
||||
;;;###autoload (defalias 'doom/describe-autodefs #'doom/help-autodefs)
|
||||
;;;###autoload (defalias 'doom/describe-module #'doom/help-modules)
|
||||
;;;###autoload (defalias 'doom/describe-package #'doom/help-packages)
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/describe-active-minor-mode (mode)
|
||||
"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 "Describe active mode: " (doom-active-minor-modes))))
|
||||
(let ((symbol
|
||||
(cond ((stringp mode) (intern mode))
|
||||
((symbolp mode) mode)
|
||||
((error "Expected a symbol/string, got a %s" (type-of mode))))))
|
||||
(if (fboundp symbol)
|
||||
(helpful-function symbol)
|
||||
(helpful-variable symbol))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Documentation commands
|
||||
|
||||
(defvar org-agenda-files)
|
||||
(defun doom--org-headings (files &optional depth include-files)
|
||||
"TODO"
|
||||
(require 'org)
|
||||
(let* ((default-directory doom-docs-dir)
|
||||
(org-agenda-files (mapcar #'expand-file-name (doom-enlist files)))
|
||||
(depth (if (integerp depth) depth))
|
||||
(org-inhibit-startup t))
|
||||
(message "Loading search results...")
|
||||
(unwind-protect
|
||||
(delq
|
||||
nil
|
||||
(org-map-entries
|
||||
(lambda ()
|
||||
(cl-destructuring-bind (level _reduced-level _todo _priority text tags)
|
||||
(org-heading-components)
|
||||
(when (and (or (null depth)
|
||||
(<= level depth))
|
||||
(or (null tags)
|
||||
(not (string-match-p ":TOC" tags))))
|
||||
(let ((path (org-get-outline-path)))
|
||||
(list (string-join
|
||||
(list (string-join
|
||||
(append (when include-files
|
||||
(list (or (+org-get-global-property "TITLE")
|
||||
(file-relative-name (buffer-file-name)))))
|
||||
path
|
||||
(when text
|
||||
(list (replace-regexp-in-string org-link-any-re "\\4" text))))
|
||||
" > ")
|
||||
tags)
|
||||
" ")
|
||||
(buffer-file-name)
|
||||
(point))))))
|
||||
t 'agenda))
|
||||
(mapc #'kill-buffer org-agenda-new-buffers)
|
||||
(setq org-agenda-new-buffers nil))))
|
||||
|
||||
(defvar ivy-sort-functions-alist)
|
||||
;;;###autoload
|
||||
(defun doom-completing-read-org-headings (prompt files &optional depth include-files initial-input extra-candidates)
|
||||
"TODO"
|
||||
(let ((alist
|
||||
(append (doom--org-headings files depth include-files)
|
||||
extra-candidates))
|
||||
ivy-sort-functions-alist)
|
||||
(if-let (result (completing-read prompt alist nil nil initial-input))
|
||||
(cl-destructuring-bind (file &optional location)
|
||||
(cdr (assoc result alist))
|
||||
(find-file file)
|
||||
(cond ((functionp location)
|
||||
(funcall location))
|
||||
(location
|
||||
(goto-char location)))
|
||||
(ignore-errors
|
||||
(when (outline-invisible-p)
|
||||
(save-excursion
|
||||
(outline-previous-visible-heading 1)
|
||||
(org-show-subtree)))))
|
||||
(user-error "Aborted"))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/homepage ()
|
||||
"Open the doom emacs homepage in the browser."
|
||||
(interactive)
|
||||
(browse-url "https://doomemacs.org"))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/help ()
|
||||
"Open Doom's user manual."
|
||||
(interactive)
|
||||
(find-file (expand-file-name "index.org" doom-docs-dir)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/help-search-headings (&optional initial-input)
|
||||
"Search Doom's documentation and jump to a headline."
|
||||
(interactive)
|
||||
(doom-completing-read-org-headings
|
||||
"Find in Doom help: "
|
||||
(list "getting_started.org"
|
||||
"contributing.org"
|
||||
"troubleshooting.org"
|
||||
"tutorials.org"
|
||||
"faq.org")
|
||||
3 t initial-input
|
||||
(mapcar (lambda (x)
|
||||
(setcar x (concat "Doom Modules > " (car x)))
|
||||
x)
|
||||
(doom--help-modules-list))))
|
||||
|
||||
;;;###autoload
|
||||
(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 '("-t" "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
|
||||
"Find in News: "
|
||||
(nreverse (doom-files-in (expand-file-name "news" doom-docs-dir)
|
||||
:match "/[0-9]"
|
||||
:relative-to doom-docs-dir))
|
||||
nil t initial-input))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/help-faq (&optional initial-input)
|
||||
"Search Doom's FAQ and jump to a question."
|
||||
(interactive)
|
||||
(doom-completing-read-org-headings
|
||||
"Find in FAQ: " (list "faq.org")
|
||||
2 nil initial-input))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/help-news ()
|
||||
"Open a Doom newsletter.
|
||||
The latest newsletter will be selected by default."
|
||||
(interactive)
|
||||
(let* ((default-directory (expand-file-name "news/" doom-docs-dir))
|
||||
(news-files (doom-files-in default-directory)))
|
||||
(find-file
|
||||
(read-file-name (format "Open Doom newsletter (current: v%s): "
|
||||
doom-version)
|
||||
default-directory
|
||||
(if (member doom-version news-files)
|
||||
doom-version
|
||||
(concat (mapconcat #'number-to-string
|
||||
(nbutlast (version-to-list doom-version) 1)
|
||||
".")
|
||||
".x"))
|
||||
t doom-version))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/help-autodefs (autodef)
|
||||
"Open documentation for an autodef.
|
||||
|
||||
An autodef is a Doom concept. It is a function or macro that is always defined,
|
||||
whether or not its containing module is disabled (in which case it will safely
|
||||
no-op without evaluating its arguments). This syntactic sugar lets you use them
|
||||
without needing to check if they are available."
|
||||
(interactive
|
||||
(let* ((settings
|
||||
(cl-loop with case-fold-search = nil
|
||||
for sym being the symbols of obarray
|
||||
for sym-name = (symbol-name sym)
|
||||
if (and (or (functionp sym)
|
||||
(macrop sym))
|
||||
(string-match-p "[a-z]!$" sym-name))
|
||||
collect sym))
|
||||
(sym (symbol-at-point))
|
||||
(autodef
|
||||
(completing-read
|
||||
"Describe setter: "
|
||||
;; TODO Could be cleaner (refactor me!)
|
||||
(cl-loop with maxwidth = (apply #'max (mapcar #'length (mapcar #'symbol-name settings)))
|
||||
for def in (sort settings #'string-lessp)
|
||||
if (get def 'doom-module)
|
||||
collect
|
||||
(format (format "%%-%ds%%s" (+ maxwidth 4))
|
||||
def (propertize (format "%s %s" (car it) (cdr it))
|
||||
'face 'font-lock-comment-face))
|
||||
else if (and (string-match-p "^set-.+!$" (symbol-name def))
|
||||
(symbol-file def)
|
||||
(file-in-directory-p (symbol-file def) doom-core-dir))
|
||||
collect
|
||||
(format (format "%%-%ds%%s" (+ maxwidth 4))
|
||||
def (propertize (format "core/%s.el" (file-name-sans-extension (file-relative-name (symbol-file def) doom-core-dir)))
|
||||
'face 'font-lock-comment-face)))
|
||||
nil t
|
||||
(when (and (symbolp sym)
|
||||
(string-match-p "!$" (symbol-name sym)))
|
||||
(symbol-name sym)))))
|
||||
(list (and autodef (car (split-string autodef " "))))))
|
||||
(or (stringp autodef)
|
||||
(functionp autodef)
|
||||
(signal 'wrong-type-argument (list '(stringp functionp) autodef)))
|
||||
(let ((fn (if (functionp autodef)
|
||||
autodef
|
||||
(intern-soft autodef))))
|
||||
(or (fboundp fn)
|
||||
(error "'%s' is not a valid DOOM autodef" autodef))
|
||||
(if (fboundp 'helpful-callable)
|
||||
(helpful-callable fn)
|
||||
(describe-function fn))))
|
||||
|
||||
(defun doom--help-modules-list ()
|
||||
(cl-loop for path in (cdr (doom-module-load-path 'all))
|
||||
for (cat . mod) = (doom-module-from-path path)
|
||||
for readme-path = (or (doom-module-locate-path cat mod "README.org")
|
||||
(doom-module-locate-path cat mod))
|
||||
for format = (format "%s %s" cat mod)
|
||||
if (doom-module-p cat mod)
|
||||
collect (list format readme-path)
|
||||
else if (and cat mod)
|
||||
collect (list (propertize format 'face 'font-lock-comment-face)
|
||||
readme-path)))
|
||||
|
||||
(defun doom--help-current-module-str ()
|
||||
(cond ((save-excursion
|
||||
(require 'smartparens)
|
||||
(ignore-errors
|
||||
(sp-beginning-of-sexp)
|
||||
(unless (eq (char-after) ?\()
|
||||
(backward-char))
|
||||
(let ((sexp (sexp-at-point)))
|
||||
(when (memq (car-safe sexp) '(featurep! require!))
|
||||
(format "%s %s" (nth 1 sexp) (nth 2 sexp)))))))
|
||||
((when buffer-file-name
|
||||
(when-let (mod (doom-module-from-path buffer-file-name))
|
||||
(unless (memq (car mod) '(:core :private))
|
||||
(format "%s %s" (car mod) (cdr mod))))))
|
||||
((when-let (mod (cdr (assq major-mode doom--help-major-mode-module-alist)))
|
||||
(format "%s %s"
|
||||
(symbol-name (car mod))
|
||||
(symbol-name (cadr mod)))))))
|
||||
|
||||
;;;###autoload
|
||||
(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
|
||||
`doom--help-major-mode-module-alist')."
|
||||
(interactive
|
||||
(nconc
|
||||
(mapcar #'intern
|
||||
(split-string
|
||||
(completing-read "Describe module: "
|
||||
(doom--help-modules-list)
|
||||
nil t nil nil
|
||||
(doom--help-current-module-str))
|
||||
" " t))
|
||||
(list current-prefix-arg)))
|
||||
(cl-check-type category symbol)
|
||||
(cl-check-type module symbol)
|
||||
(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))
|
||||
(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")))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/help-custom-variable (var)
|
||||
"Look up documentation for a custom variable.
|
||||
|
||||
Unlike `helpful-variable', which casts a wider net that includes internal
|
||||
variables, this only lists variables that exist to be customized (defined with
|
||||
`defcustom')."
|
||||
(interactive
|
||||
(list (helpful--read-symbol
|
||||
"Custom variable: "
|
||||
(helpful--variable-at-point)
|
||||
(lambda (sym)
|
||||
(and (helpful--variable-p sym)
|
||||
(custom-variable-p sym)
|
||||
;; Exclude minor mode state variables, which aren't meant to be
|
||||
;; modified directly, but through their associated function.
|
||||
(not (or (and (string-suffix-p "-mode" (symbol-name sym))
|
||||
(fboundp sym))
|
||||
(eq (get sym 'custom-set) 'custom-set-minor-mode))))))))
|
||||
(helpful-variable var))
|
||||
|
||||
|
||||
;;
|
||||
;;; `doom/help-packages'
|
||||
|
||||
(defun doom--help-insert-button (label &optional path)
|
||||
(cl-destructuring-bind (uri . qs)
|
||||
(let ((parts (split-string label "::" t)))
|
||||
(cons (string-trim (car parts))
|
||||
(string-join (cdr parts) "::")))
|
||||
(let ((path (or path label)))
|
||||
(insert-text-button
|
||||
uri
|
||||
'face 'link
|
||||
'follow-link t
|
||||
'action
|
||||
(lambda (_)
|
||||
(when (window-dedicated-p)
|
||||
(other-window 1))
|
||||
(pcase (cond ((string-match-p "^https?://" qs) 'url)
|
||||
('file))
|
||||
((or `file `nil)
|
||||
(unless (file-exists-p path)
|
||||
(user-error "Path does not exist: %S" path))
|
||||
(let ((buffer (or (get-file-buffer path)
|
||||
(find-file path))))
|
||||
(when qs
|
||||
(with-current-buffer buffer
|
||||
(goto-char (point-min))
|
||||
(re-search-forward qs)
|
||||
(recenter)))))
|
||||
(`url (browse-url uri))))))))
|
||||
|
||||
(defun doom--help-package-configs (package)
|
||||
(let ((default-directory doom-emacs-dir))
|
||||
;; TODO Use ripgrep instead
|
||||
(split-string
|
||||
(cdr (doom-call-process
|
||||
"git" "grep" "--no-break" "--no-heading" "--line-number"
|
||||
(format "%s %s\\($\\| \\)"
|
||||
"\\(^;;;###package\\|(after!\\|(use-package!\\)"
|
||||
package)
|
||||
":(exclude)*.org"))
|
||||
"\n" t)))
|
||||
|
||||
(defvar doom--help-packages-list nil)
|
||||
;;;###autoload
|
||||
(defun doom/help-packages (package)
|
||||
"Like `describe-package', but for packages installed by Doom modules.
|
||||
|
||||
Only shows installed packages. Includes information about where packages are
|
||||
defined and configured.
|
||||
|
||||
If prefix arg is present, refresh the cache."
|
||||
(interactive
|
||||
(let ((guess (or (function-called-at-point)
|
||||
(symbol-at-point))))
|
||||
(require 'finder-inf nil t)
|
||||
(require 'package)
|
||||
(require 'straight)
|
||||
(let ((packages
|
||||
(if (and doom--help-packages-list (null current-prefix-arg))
|
||||
doom--help-packages-list
|
||||
(message "Generating packages list for the first time...")
|
||||
(sit-for 0.1)
|
||||
(setq doom--help-packages-list
|
||||
(delete-dups
|
||||
(append (mapcar #'car package-alist)
|
||||
(mapcar #'car package--builtins)
|
||||
(mapcar #'intern (hash-table-keys straight--build-cache))
|
||||
(mapcar #'car (doom-package-list 'all))
|
||||
nil))))))
|
||||
(unless (memq guess packages)
|
||||
(setq guess nil))
|
||||
(list
|
||||
(intern
|
||||
(completing-read (if guess
|
||||
(format "Select Doom package to search for (default %s): "
|
||||
guess)
|
||||
(format "Describe Doom package (%d): " (length packages)))
|
||||
packages nil t nil nil
|
||||
(if guess (symbol-name guess))))))))
|
||||
;; TODO Refactor me.
|
||||
(require 'core-packages)
|
||||
(doom-initialize-packages)
|
||||
(if (or (package-desc-p package)
|
||||
(and (symbolp package)
|
||||
(or (assq package package-alist)
|
||||
(assq package package--builtins))))
|
||||
(describe-package package)
|
||||
(help-setup-xref (list #'doom/help-packages package)
|
||||
(called-interactively-p 'interactive))
|
||||
(with-help-window (help-buffer)))
|
||||
(save-excursion
|
||||
(with-current-buffer (help-buffer)
|
||||
(let ((inhibit-read-only t)
|
||||
(indent (make-string 13 ? )))
|
||||
(goto-char (point-max))
|
||||
(if (re-search-forward "^ *Status: " nil t)
|
||||
(progn
|
||||
(end-of-line)
|
||||
(insert "\n"))
|
||||
(search-forward "\n\n" nil t))
|
||||
|
||||
(package--print-help-section "Package")
|
||||
(insert (symbol-name package) "\n")
|
||||
|
||||
(package--print-help-section "Source")
|
||||
(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")
|
||||
(let ((default-directory (straight--repos-dir (symbol-name package))))
|
||||
(insert (cdr (doom-call-process "git" "log" "-1" "--format=%D %h %ci"))
|
||||
"\n" indent))
|
||||
(package--print-help-section "Build location")
|
||||
(let ((build-dir (straight--build-dir (symbol-name package))))
|
||||
(if (file-exists-p build-dir)
|
||||
(doom--help-insert-button (abbreviate-file-name build-dir))
|
||||
(insert "n/a")))
|
||||
(insert "\n" indent)
|
||||
(package--print-help-section "Repo location")
|
||||
(let ((repo-dir (straight--repos-dir (symbol-name package))))
|
||||
(if (file-exists-p repo-dir)
|
||||
(doom--help-insert-button (abbreviate-file-name repo-dir))
|
||||
(insert "n/a"))
|
||||
(insert "\n"))
|
||||
(let ((recipe (doom-package-build-recipe package)))
|
||||
(package--print-help-section "Recipe")
|
||||
(insert (format "%s\n" (string-trim (pp-to-string recipe))))
|
||||
(package--print-help-section "Homepage")
|
||||
(doom--help-insert-button (doom--package-url package))))
|
||||
(`elpa (insert "[M]ELPA ")
|
||||
(doom--help-insert-button (doom--package-url package))
|
||||
(package--print-help-section "Location")
|
||||
(doom--help-insert-button
|
||||
(abbreviate-file-name
|
||||
(file-name-directory (locate-library (symbol-name package))))))
|
||||
(`builtin (insert "Built-in\n")
|
||||
(package--print-help-section "Location")
|
||||
(doom--help-insert-button
|
||||
(abbreviate-file-name
|
||||
(file-name-directory (locate-library (symbol-name package))))))
|
||||
(`other (doom--help-insert-button
|
||||
(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 modules)
|
||||
(let* ((module-path (pcase (car m)
|
||||
(:core doom-core-dir)
|
||||
(:private doom-private-dir)
|
||||
(category (doom-module-path category (cdr m)))))
|
||||
(readme-path (expand-file-name "README.org" module-path)))
|
||||
(insert indent)
|
||||
(doom--help-insert-button
|
||||
(format "%s %s" (car m) (or (cdr m) ""))
|
||||
module-path)
|
||||
(insert " (")
|
||||
(if (file-exists-p readme-path)
|
||||
(doom--help-insert-button
|
||||
"readme"
|
||||
(expand-file-name
|
||||
"README.org"
|
||||
readme-path))
|
||||
(insert "no readme"))
|
||||
(insert ")\n"))))
|
||||
|
||||
(package--print-help-section "Configs")
|
||||
(insert "This package is configured in the following locations:")
|
||||
(dolist (location (doom--help-package-configs package))
|
||||
(insert "\n" indent)
|
||||
(insert-text-button
|
||||
location
|
||||
'face 'link
|
||||
'follow-link t
|
||||
'action
|
||||
`(lambda (_)
|
||||
(cl-destructuring-bind (file line _match)
|
||||
',(split-string location ":")
|
||||
(find-file (expand-file-name file doom-emacs-dir))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (string-to-number line)))
|
||||
(recenter)))))
|
||||
|
||||
(insert "\n\n")))))
|
||||
|
||||
(defvar doom--package-cache nil)
|
||||
(defun doom--package-list (&optional prompt)
|
||||
(let* ((guess (or (function-called-at-point)
|
||||
(symbol-at-point))))
|
||||
(require 'finder-inf nil t)
|
||||
(unless package--initialized
|
||||
(package-initialize t))
|
||||
(let ((packages (or doom--package-cache
|
||||
(progn
|
||||
(message "Reading packages...")
|
||||
(cl-delete-duplicates
|
||||
(append (mapcar 'car package-alist)
|
||||
(mapcar 'car package--builtins)
|
||||
(mapcar 'car package-archive-contents)))))))
|
||||
(setq doom--package-cache packages)
|
||||
(unless (memq guess packages)
|
||||
(setq guess nil))
|
||||
(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)))))))
|
||||
|
||||
(defun doom--package-url (package)
|
||||
(cond ((assq package package--builtins)
|
||||
(user-error "Package is built into Emacs and cannot be looked up"))
|
||||
((when-let (location (locate-library (symbol-name package)))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (concat (file-name-sans-extension location) ".el")
|
||||
nil 0 4096)
|
||||
(let ((case-fold-search t))
|
||||
(when (re-search-forward " \\(?:URL\\|homepage\\|Website\\): \\(http[^\n]+\\)\n" nil t)
|
||||
(match-string-no-properties 1))))))
|
||||
((and (ignore-errors (eq (doom-package-backend package) 'quelpa))
|
||||
(let* ((plist (cdr (doom-package-prop package :recipe)))
|
||||
(fetcher (plist-get plist :fetcher)))
|
||||
(pcase fetcher
|
||||
(`git (plist-get plist :url))
|
||||
(`github (format "https://github.com/%s.git" (plist-get plist :repo)))
|
||||
(`gitlab (format "https://gitlab.com/%s.git" (plist-get plist :repo)))
|
||||
(`bitbucket (format "https://bitbucket.com/%s" (plist-get plist :repo)))
|
||||
(`wiki (format "https://www.emacswiki.org/emacs/download/%s"
|
||||
(or (car-safe (doom-enlist (plist-get plist :files)))
|
||||
(format "%s.el" package))))
|
||||
(_ (plist-get plist :url))))))
|
||||
((and (require 'package nil t)
|
||||
(or package-archive-contents
|
||||
(progn (package-refresh-contents)
|
||||
package-archive-contents))
|
||||
(pcase (package-desc-archive (cadr (assq package package-archive-contents)))
|
||||
("org" "https://orgmode.org")
|
||||
((or "melpa" "melpa-mirror")
|
||||
(format "https://melpa.org/#/%s" package))
|
||||
("gnu"
|
||||
(format "https://elpa.gnu.org/packages/%s.html" package))
|
||||
(archive
|
||||
(if-let (src (cdr (assoc package package-archives)))
|
||||
(format "%s" src)
|
||||
(user-error "%S isn't installed through any known source (%s)"
|
||||
package archive))))))
|
||||
((user-error "Cannot find the homepage for %S" package))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/help-package-config (package)
|
||||
"Jump to any `use-package!', `after!' or ;;;###package block for PACKAGE.
|
||||
|
||||
This only searches `doom-emacs-dir' (typically ~/.emacs.d) and does not include
|
||||
config blocks in your private config."
|
||||
(interactive (list (doom--package-list "Find package config: ")))
|
||||
(cl-destructuring-bind (file line _match)
|
||||
(split-string
|
||||
(completing-read
|
||||
"Jump to config: "
|
||||
(or (doom--help-package-configs package)
|
||||
(user-error "This package isn't configured by you or Doom")))
|
||||
":")
|
||||
(find-file (expand-file-name file doom-emacs-dir))
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- line))
|
||||
(recenter)))
|
||||
|
||||
;;;###autoload
|
||||
(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
|
||||
(if (stringp counsel-rg-base-command)
|
||||
(format counsel-rg-base-command
|
||||
(concat "%s " (mapconcat #'shell-quote-argument dirs " ")))
|
||||
(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
|
||||
(list (doom--help-search-prompt "Search load-path: ")))
|
||||
(doom--help-search (cl-remove-if-not #'file-directory-p load-path)
|
||||
query "Search load-path: "))
|
||||
|
||||
;;;###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
|
||||
(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! (or (format "%s.el.gz" filebase)
|
||||
(format "%s.el" filebase)))
|
||||
collect it)
|
||||
query "Search loaded files: "))
|
|
@ -0,0 +1,276 @@
|
|||
;;; core/autoload/output.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-output-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-output-class-alist
|
||||
`((color . doom--output-color)
|
||||
(class . doom--output-class)
|
||||
(indent . doom--output-indent)
|
||||
(autofill . doom--output-autofill)
|
||||
|
||||
(success . (lambda (str &rest args)
|
||||
(apply #'doom--output-color 'green (format "✓ %s" str) args)))
|
||||
(warn . (lambda (str &rest args)
|
||||
(apply #'doom--output-color 'yellow (format "! %s" str) args)))
|
||||
(error . (lambda (str &rest args)
|
||||
(apply #'doom--output-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-p
|
||||
(apply #'doom--output-color 'dark
|
||||
(format "- %s" str)
|
||||
args)
|
||||
"")))
|
||||
(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-output-indent 0
|
||||
"Level to rigidly indent text returned by `format!' and `print!'.")
|
||||
|
||||
(defvar doom-output-indent-increment 2
|
||||
"Steps in which to increment `doom-output-indent' for consecutive levels.")
|
||||
|
||||
(defvar doom-output-backend
|
||||
(if doom-interactive-p 'text-properties 'ansi)
|
||||
"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-output-indent 32)
|
||||
(replace-regexp-in-string
|
||||
"\n" (concat "\n" (make-string doom-output-indent 32))
|
||||
output t t))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom--print (output)
|
||||
(unless (string-empty-p output)
|
||||
(princ output)
|
||||
(terpri)
|
||||
t))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom--output-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--output-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--output-color (style format &rest args)
|
||||
"Apply STYLE to formatted MESSAGE with ARGS.
|
||||
|
||||
STYLE is a symbol that correlates to `doom-output-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-output-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-output-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-output-ansi-alist))))
|
||||
((>= code 30)
|
||||
`(:foreground ,(face-foreground (caddr (assq style doom-output-ansi-alist)))))
|
||||
((cddr (assq style doom-output-ansi-alist)))))))
|
||||
(_ message))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom--output-class (class format &rest args)
|
||||
"Apply CLASS to formatted format with ARGS.
|
||||
|
||||
CLASS is derived from `doom-output-class-alist', and can contain any arbitrary,
|
||||
transformative logic."
|
||||
(let (fn)
|
||||
(cond ((setq fn (cdr (assq class doom-output-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--output-apply (forms &optional sub)
|
||||
"Replace color-name functions with calls to `doom--output-color'."
|
||||
(cond ((null forms) nil)
|
||||
((listp forms)
|
||||
(append (cond ((not (symbolp (car forms)))
|
||||
(list (doom--output-apply (car forms))))
|
||||
(sub
|
||||
(list (car forms)))
|
||||
((assq (car forms) doom-output-ansi-alist)
|
||||
`(doom--output-color ',(car forms)))
|
||||
((assq (car forms) doom-output-class-alist)
|
||||
`(doom--output-class ',(car forms)))
|
||||
((list (car forms))))
|
||||
(doom--output-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--output-apply `(,message ,@args)))))
|
||||
|
||||
;;;###autoload
|
||||
(defmacro print-group! (&rest body)
|
||||
"Indents any `print!' or `format!' output within BODY."
|
||||
`(let ((doom-output-indent (+ doom-output-indent-increment doom-output-indent)))
|
||||
,@body))
|
||||
|
||||
;;;###autoload
|
||||
(defmacro print! (message &rest args)
|
||||
"Prints MESSAGE, formatted with ARGS, to stdout.
|
||||
|
||||
Returns non-nil if the message is a non-empty string.
|
||||
|
||||
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--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)))
|
||||
|
||||
;;;###autoload
|
||||
(defmacro with-output-to! (dest &rest body)
|
||||
"Send all output produced in BODY to DEST.
|
||||
DEST can be one or more of `standard-output', a buffer, a file"
|
||||
(declare (indent 1))
|
||||
`(let* ((log-buffer (generate-new-buffer " *doom log*"))
|
||||
(standard-output
|
||||
(lambda (out)
|
||||
(with-current-buffer log-buffer
|
||||
(insert-char out))
|
||||
(send-string-to-terminal (char-to-string out)))))
|
||||
(letf! (defun message (msg &rest args)
|
||||
(with-current-buffer log-buffer
|
||||
(print-group!
|
||||
(insert (doom--format (apply #'format msg args)) "\n")))
|
||||
(if doom-debug-p
|
||||
(doom--print (doom--format (apply #'format msg args)))
|
||||
(apply message msg args)))
|
||||
(unwind-protect
|
||||
,(macroexp-progn body)
|
||||
(with-current-buffer log-buffer
|
||||
(require 'ansi-color)
|
||||
(ansi-color-filter-region (point-min) (point-max)))
|
||||
(let ((dest ,dest))
|
||||
(cond ((bufferp dest)
|
||||
(with-current-buffer dest
|
||||
(insert-buffer-substring log-buffer)))
|
||||
((stringp dest)
|
||||
(make-directory (file-name-directory dest) 'parents)
|
||||
(with-temp-file dest
|
||||
(insert-buffer-substring log-buffer))))
|
||||
(kill-buffer log-buffer))))))
|
|
@ -0,0 +1,202 @@
|
|||
;;; core/autoload/packages.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/reload-packages ()
|
||||
"Reload `doom-packages', `package' and `quelpa'."
|
||||
(interactive)
|
||||
;; HACK straight.el must be loaded for this to work
|
||||
(message "Reloading packages")
|
||||
(doom-initialize-packages t)
|
||||
(message "Reloading packages...DONE"))
|
||||
|
||||
|
||||
;;
|
||||
;;; Bump commands
|
||||
|
||||
(defun doom--package-merge-recipes (package plist)
|
||||
(require 'straight)
|
||||
(doom-plist-merge
|
||||
(plist-get plist :recipe)
|
||||
(if-let (recipe (straight-recipes-retrieve package))
|
||||
(cdr (if (memq (car recipe) '(quote \`))
|
||||
(eval recipe t)
|
||||
recipe))
|
||||
(let ((recipe (plist-get (cdr (assq package doom-packages))
|
||||
:recipe)))
|
||||
(if (keywordp (car recipe))
|
||||
recipe
|
||||
(cdr 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-merge-recipes 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-merge-recipes package plist))
|
||||
(branch (plist-get recipe :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 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 (intern (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))
|
|
@ -0,0 +1,75 @@
|
|||
;;; core/autoload/plist.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Macros
|
||||
|
||||
;;;###autoload
|
||||
(cl-defmacro doplist! ((arglist plist &optional retval) &rest body)
|
||||
"Loop over a PLIST's (property value) pairs then return RETVAL.
|
||||
|
||||
Evaluate BODY with either ARGLIST bound to (cons PROP VAL) or, if ARGLIST is a
|
||||
list, the pair is destructured into (CAR . CDR)."
|
||||
(declare (indent 1))
|
||||
(let ((plist-var (make-symbol "plist")))
|
||||
`(let ((,plist-var (copy-sequence ,plist)))
|
||||
(while ,plist-var
|
||||
(let ,(if (listp arglist)
|
||||
`((,(pop arglist) (pop ,plist-var))
|
||||
(,(pop arglist) (pop ,plist-var)))
|
||||
`((,arglist (cons (pop ,plist-var)
|
||||
(pop ,plist-var)))))
|
||||
,@body))
|
||||
,retval)))
|
||||
|
||||
;;;###autoload
|
||||
(defmacro plist-put! (plist &rest rest)
|
||||
"Set each PROP VALUE pair in REST to PLIST in-place."
|
||||
`(cl-loop for (prop value)
|
||||
on (list ,@rest) by #'cddr
|
||||
do ,(if (symbolp plist)
|
||||
`(setq ,plist (plist-put ,plist prop value))
|
||||
`(plist-put ,plist prop value))))
|
||||
|
||||
;;;###autoload
|
||||
(defmacro plist-delete! (plist prop)
|
||||
"Delete PROP from PLIST in-place."
|
||||
`(setq ,plist (doom-plist-delete ,plist ,prop)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Library
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-plist-get (plist prop &optional nil-value)
|
||||
"Return PROP in PLIST, if it exists. Otherwise NIL-VALUE."
|
||||
(if-let (val (plist-member plist prop))
|
||||
(cadr val)
|
||||
nil-value))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-plist-merge (from-plist 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)))
|
||||
to-plist))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-plist-delete-nil (plist)
|
||||
"Delete `nil' properties from a copy of PLIST."
|
||||
(let (p)
|
||||
(while plist
|
||||
(if (car plist)
|
||||
(plist-put! p (car plist) (nth 1 plist)))
|
||||
(setq plist (cddr plist)))
|
||||
p))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-plist-delete (plist &rest props)
|
||||
"Delete PROPS from a copy of PLIST."
|
||||
(let (p)
|
||||
(while plist
|
||||
(if (not (memq (car plist) props))
|
||||
(plist-put! p (car plist) (nth 1 plist)))
|
||||
(setq plist (cddr plist)))
|
||||
p))
|
|
@ -0,0 +1,70 @@
|
|||
;;; core/autoload/process.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###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 (remq 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 (remq nil 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)))))
|
||||
|
||||
(defvar doom--num-cpus nil)
|
||||
;;;###autoload
|
||||
(defun doom-num-cpus ()
|
||||
"Return the max number of processing units on this system.
|
||||
Tries to be portable. Returns 1 if cannot be determined."
|
||||
(or doom--num-cpus
|
||||
(setq doom--num-cpus
|
||||
(let ((cpus
|
||||
(cond ((getenv "NUMBER_OF_PROCESSORS"))
|
||||
((executable-find "nproc")
|
||||
(doom-call-process "nproc"))
|
||||
((executable-find "sysctl")
|
||||
(doom-call-process "sysctl" "-n" "hw.ncpu")))))
|
||||
(max
|
||||
1 (or (cl-typecase cpus
|
||||
(string
|
||||
(condition-case _
|
||||
(string-to-number cpus)
|
||||
(wrong-type-argument
|
||||
(user-error "NUMBER_OF_PROCESSORS contains an invalid value: %S"
|
||||
cpus))))
|
||||
(consp
|
||||
(if (zerop (car cpus))
|
||||
(string-to-number (cdr cpus))
|
||||
(user-error "Failed to look up number of processors, because:\n\n%s"
|
||||
(cdr cpus)))))
|
||||
1))))))
|
|
@ -0,0 +1,144 @@
|
|||
;;; 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")
|
||||
|
||||
;;;###autodef
|
||||
(cl-defun set-project-type! (name &key predicate compile run test configure dir)
|
||||
"Add a project type to `projectile-project-type'."
|
||||
(declare (indent 1))
|
||||
(after! projectile
|
||||
(add-to-list 'projectile-project-types
|
||||
(list name
|
||||
'marker-files predicate
|
||||
'compilation-dir dir
|
||||
'configure-command configure
|
||||
'compile-command compile
|
||||
'test-command test
|
||||
'run-command run))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Macros
|
||||
|
||||
;;;###autoload
|
||||
(defmacro project-file-exists-p! (files)
|
||||
"Checks if the project has the specified FILES.
|
||||
Paths are relative to the project root, unless they start with ./ or ../ (in
|
||||
which case they're relative to `default-directory'). If they start with a slash,
|
||||
they are absolute."
|
||||
`(file-exists-p! ,files (doom-project-root)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/find-file-in-other-project (project-root)
|
||||
"Preforms `projectile-find-file' in a known project of your choosing."
|
||||
(interactive
|
||||
(list
|
||||
(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))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/browse-in-other-project (project-root)
|
||||
"Preforms `find-file' in a known project of your choosing."
|
||||
(interactive
|
||||
(list
|
||||
(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))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/browse-in-emacsd ()
|
||||
"Browse files from `doom-emacs-dir'."
|
||||
(interactive) (doom-project-browse doom-emacs-dir))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/find-file-in-emacsd ()
|
||||
"Find a file under `doom-emacs-dir', recursively."
|
||||
(interactive) (doom-project-find-file doom-emacs-dir))
|
||||
|
||||
|
||||
;;
|
||||
;;; Library
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-project-p (&optional dir)
|
||||
"Return t if DIR (defaults to `default-directory') is a valid project."
|
||||
(and (doom-project-root dir)
|
||||
t))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-project-root (&optional dir)
|
||||
"Return the project root of DIR (defaults to `default-directory').
|
||||
Returns nil if not in a project."
|
||||
(let ((projectile-project-root (unless dir projectile-project-root))
|
||||
projectile-require-project-root)
|
||||
(projectile-project-root dir)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-project-name (&optional dir)
|
||||
"Return the name of the current project.
|
||||
|
||||
Returns '-' if not in a valid project."
|
||||
(if-let (project-root (or (doom-project-root dir)
|
||||
(if dir (expand-file-name dir))))
|
||||
(funcall projectile-project-name-function project-root)
|
||||
"-"))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-project-expand (name &optional dir)
|
||||
"Expand NAME to project root."
|
||||
(expand-file-name name (doom-project-root dir)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-project-find-file (dir)
|
||||
"Jump to a file in DIR (searched recursively).
|
||||
|
||||
If DIR is not a project, it will be indexed (but not cached)."
|
||||
(unless (file-directory-p dir)
|
||||
(error "Directory %S does not exist" dir))
|
||||
(unless (file-readable-p dir)
|
||||
(error "Directory %S isn't readable" dir))
|
||||
(let* ((default-directory (file-truename dir))
|
||||
(projectile-project-root (doom-project-root dir))
|
||||
(projectile-enable-caching projectile-enable-caching))
|
||||
(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.
|
||||
(setq projectile-enable-caching nil))
|
||||
(call-interactively
|
||||
;; Intentionally avoid `helm-projectile-find-file', because it runs
|
||||
;; asynchronously, and thus doesn't see the lexical
|
||||
;; `default-directory'
|
||||
(if (doom-module-p :completion 'ivy)
|
||||
#'counsel-projectile-find-file
|
||||
#'projectile-find-file)))
|
||||
((fboundp 'counsel-file-jump) ; ivy only
|
||||
(call-interactively #'counsel-file-jump))
|
||||
((project-current nil dir)
|
||||
(project-find-file-in nil nil dir))
|
||||
((fboundp 'helm-find-files)
|
||||
(call-interactively #'helm-find-files))
|
||||
((call-interactively #'find-file)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-project-browse (dir)
|
||||
"Traverse a file structure starting linearly from DIR."
|
||||
(let ((default-directory (file-truename (expand-file-name dir))))
|
||||
(call-interactively
|
||||
(cond ((doom-module-p :completion 'ivy)
|
||||
#'counsel-find-file)
|
||||
((doom-module-p :completion 'helm)
|
||||
#'helm-find-files)
|
||||
(#'find-file)))))
|
|
@ -0,0 +1,199 @@
|
|||
;;; core/autoload/scratch.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-scratch-default-file "__default"
|
||||
"The default file name for a project-less scratch buffer.
|
||||
|
||||
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-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'
|
||||
MAJOR-MODE Any major mode symbol")
|
||||
|
||||
(defvar doom-scratch-buffers nil
|
||||
"A list of active scratch buffers.")
|
||||
|
||||
(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 (project-name)
|
||||
(setq-local doom-scratch-current-project
|
||||
(or project-name
|
||||
doom-scratch-default-file))
|
||||
(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 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 dont-restore-p mode directory project-name)
|
||||
"Return a scratchpad buffer in major MODE."
|
||||
(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))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Persistent scratch buffer
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-persist-scratch-buffer-h ()
|
||||
"Save the current buffer to `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 ()
|
||||
"Save all scratch buffers to `doom-scratch-dir'."
|
||||
(setq doom-scratch-buffers
|
||||
(cl-delete-if-not #'buffer-live-p doom-scratch-buffers))
|
||||
(dolist (buffer doom-scratch-buffers)
|
||||
(with-current-buffer buffer
|
||||
(doom-persist-scratch-buffer-h))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-persist-scratch-buffers-after-switch-h ()
|
||||
"Kill scratch buffers when they are no longer visible, saving them to disk."
|
||||
(unless (cl-some #'get-buffer-window doom-scratch-buffers)
|
||||
(mapc #'kill-buffer doom-scratch-buffers)
|
||||
(remove-hook 'doom-switch-buffer-hook #'doom-persist-scratch-buffers-after-switch-h)))
|
||||
|
||||
;;;###autoload
|
||||
(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 same-window-p)
|
||||
"Pop up a persistent scratch buffer.
|
||||
|
||||
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 same-window-p
|
||||
#'switch-to-buffer
|
||||
#'pop-to-buffer)
|
||||
(doom-scratch-buffer
|
||||
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-initial-major-mode)
|
||||
nil)
|
||||
((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 arg project-p)
|
||||
"Like `doom/open-scratch-buffer', but switches to it in the current window.
|
||||
|
||||
If passed the prefix ARG, do not restore the last scratch buffer."
|
||||
(interactive "P")
|
||||
(doom/open-scratch-buffer arg project-p 'same-window))
|
||||
|
||||
;;;###autoload
|
||||
(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, do not restore the last scratch buffer."
|
||||
(interactive "P")
|
||||
(doom/open-scratch-buffer arg 'project same-window-p))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/switch-to-project-scratch-buffer (&optional arg)
|
||||
"Like `doom/open-project-scratch-buffer', but switches to it in the current
|
||||
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 ()
|
||||
"Revert scratch buffer to last persistent state."
|
||||
(interactive)
|
||||
(unless (string-match-p "^\\*doom:scratch" (buffer-name))
|
||||
(user-error "Not in a scratch buffer"))
|
||||
(when (doom--load-persistent-scratch-buffer doom-scratch-current-project)
|
||||
(message "Reloaded scratch buffer")))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/delete-persistent-scratch-file (&optional arg)
|
||||
"Deletes a scratch buffer file in `doom-scratch-dir'.
|
||||
|
||||
If prefix ARG, delete all persistent scratches."
|
||||
(interactive)
|
||||
(if arg
|
||||
(progn
|
||||
(delete-directory doom-scratch-dir t)
|
||||
(message "Cleared %S" (abbreviate-file-name doom-scratch-dir)))
|
||||
(make-directory doom-scratch-dir t)
|
||||
(let ((file (read-file-name "Delete scratch file > " doom-scratch-dir "scratch")))
|
||||
(if (not (file-exists-p file))
|
||||
(message "%S does not exist" (abbreviate-file-name file))
|
||||
(delete-file file)
|
||||
(message "Successfully deleted %S" (abbreviate-file-name file))))))
|
|
@ -0,0 +1,143 @@
|
|||
;;; core/autoload/sessions.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar desktop-base-file-name)
|
||||
(defvar desktop-dirname)
|
||||
(defvar desktop-restore-eager)
|
||||
(defvar desktop-file-modtime)
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-session-file (&optional name)
|
||||
"TODO"
|
||||
(cond ((require 'persp-mode nil t)
|
||||
(expand-file-name (or name persp-auto-save-fname) persp-save-dir))
|
||||
((require 'desktop nil t)
|
||||
(if name
|
||||
(expand-file-name name (file-name-directory (desktop-full-file-name)))
|
||||
(desktop-full-file-name)))
|
||||
((error "No session backend available"))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-save-session (&optional file)
|
||||
"TODO"
|
||||
(setq file (expand-file-name (or file (doom-session-file))))
|
||||
(cond ((require 'persp-mode nil t)
|
||||
(unless persp-mode (persp-mode +1))
|
||||
(setq persp-auto-save-opt 0)
|
||||
(persp-save-state-to-file file))
|
||||
((and (require 'frameset nil t)
|
||||
(require 'restart-emacs nil t))
|
||||
(let ((frameset-filter-alist (append '((client . restart-emacs--record-tty-file))
|
||||
frameset-filter-alist))
|
||||
(desktop-base-file-name (file-name-nondirectory file))
|
||||
(desktop-dirname (file-name-directory file))
|
||||
(desktop-restore-eager t)
|
||||
desktop-file-modtime)
|
||||
(make-directory desktop-dirname t)
|
||||
;; Prevents confirmation prompts
|
||||
(let ((desktop-file-modtime (nth 5 (file-attributes (desktop-full-file-name)))))
|
||||
(desktop-save desktop-dirname t))))
|
||||
((error "No session backend to save session with"))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-load-session (&optional file)
|
||||
"TODO"
|
||||
(setq file (expand-file-name (or file (doom-session-file))))
|
||||
(message "Attempting to load %s" file)
|
||||
(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))
|
||||
(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))
|
||||
((error "No session backend to load session with"))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Command line switch
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-restore-session-handler (&rest _)
|
||||
"TODO"
|
||||
(add-hook 'window-setup-hook #'doom-load-session 'append))
|
||||
|
||||
;;;###autoload
|
||||
(add-to-list 'command-switch-alist (cons "--restore" #'doom-restore-session-handler))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/quickload-session ()
|
||||
"TODO"
|
||||
(interactive)
|
||||
(message "Restoring session...")
|
||||
(doom-load-session)
|
||||
(message "Session restored. Welcome back."))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/quicksave-session ()
|
||||
"TODO"
|
||||
(interactive)
|
||||
(message "Saving session")
|
||||
(doom-save-session)
|
||||
(message "Saving session...DONE"))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/load-session (file)
|
||||
"TODO"
|
||||
(interactive
|
||||
(let ((session-file (doom-session-file)))
|
||||
(list (or (read-file-name "Session to restore: "
|
||||
(file-name-directory session-file)
|
||||
(file-name-nondirectory session-file)
|
||||
t)
|
||||
(user-error "No session selected. Aborting")))))
|
||||
(unless file
|
||||
(error "No session file selected"))
|
||||
(message "Loading '%s' session" file)
|
||||
(doom-load-session file)
|
||||
(message "Session restored. Welcome back."))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/save-session (file)
|
||||
"TODO"
|
||||
(interactive
|
||||
(let ((session-file (doom-session-file)))
|
||||
(list (or (read-file-name "Save session to: "
|
||||
(file-name-directory session-file)
|
||||
(file-name-nondirectory session-file))
|
||||
(user-error "No session selected. Aborting")))))
|
||||
(unless file
|
||||
(error "No session file selected"))
|
||||
(message "Saving '%s' session" file)
|
||||
(doom-save-session file))
|
||||
|
||||
;;;###autoload
|
||||
(defalias 'doom/restart #'restart-emacs)
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/restart-and-restore (&optional debug)
|
||||
"TODO"
|
||||
(interactive "P")
|
||||
(setq doom-autosave-session nil)
|
||||
(doom/quicksave-session)
|
||||
(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")))))
|
|
@ -0,0 +1,143 @@
|
|||
;;; core/autoload/cache.el -*- lexical-binding: t; -*-
|
||||
|
||||
;; This little library abstracts the process of writing arbitrary elisp values
|
||||
;; to a 2-tiered file store (in `doom-store-dir'/`doom-store-location').
|
||||
|
||||
(defvar doom-store-dir (concat doom-etc-dir "store/")
|
||||
"Directory to look for and store data accessed through this API.")
|
||||
|
||||
(defvar doom-store-persist-alist '(t)
|
||||
"An alist of alists, containing lists of variables for the doom cache library
|
||||
to persist across Emacs sessions.")
|
||||
|
||||
(defvar doom-store-location "default"
|
||||
"The default location for cache files. This symbol is translated into a file
|
||||
name under `pcache-directory' (by default a subdirectory under
|
||||
`doom-store-dir'). One file may contain multiple cache entries.")
|
||||
|
||||
(defvar doom--store-table (make-hash-table :test 'equal))
|
||||
(defvar doom--inhibit-flush nil)
|
||||
|
||||
(defun doom-save-persistent-store-h ()
|
||||
"Hook to run when an Emacs session is killed. Saves all persisted variables
|
||||
listed in `doom-store-persist-alist' to files."
|
||||
(let (locations)
|
||||
(let ((doom--inhibit-flush t))
|
||||
(dolist (alist (butlast doom-store-persist-alist 1))
|
||||
(cl-loop with location = (car alist)
|
||||
for var in (cdr alist)
|
||||
do (doom-store-put var (symbol-value var) nil location)
|
||||
and do (cl-pushnew location locations))))
|
||||
(mapc #'doom--store-flush locations)))
|
||||
(add-hook 'kill-emacs-hook #'doom-save-persistent-store-h)
|
||||
|
||||
|
||||
;;
|
||||
;; Library
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-store-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. This cannot persist buffer-local variables."
|
||||
(dolist (var variables)
|
||||
(when (doom-store-member-p var location)
|
||||
(set var (doom-store-get var location))))
|
||||
(setf (alist-get location doom-store-persist-alist)
|
||||
(append variables (alist-get location doom-store-persist-alist))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-store-desist (location &optional variables)
|
||||
"Unregisters VARIABLES (list of symbols) in LOCATION (symbol).
|
||||
Variables to persist are recorded in `doom-store-persist-alist'. Does not affect
|
||||
the actual variables themselves or their values."
|
||||
(if variables
|
||||
(setf (alist-get location doom-store-persist-alist)
|
||||
(cl-set-difference (cdr (assq location doom-store-persist-alist))
|
||||
variables))
|
||||
(delq! location doom-store-persist-alist 'assoc)))
|
||||
|
||||
(defun doom--store-init (location)
|
||||
(or (gethash location doom--store-table)
|
||||
(let* ((file-name-handler-alist nil)
|
||||
(location-path (expand-file-name location doom-store-dir)))
|
||||
(if (file-exists-p location-path)
|
||||
(puthash location
|
||||
(with-temp-buffer
|
||||
(set-buffer-multibyte nil)
|
||||
(setq buffer-file-coding-system 'binary)
|
||||
(insert-file-contents-literally location-path)
|
||||
(read (current-buffer)))
|
||||
doom--store-table)
|
||||
(puthash location (make-hash-table :test 'equal)
|
||||
doom--store-table)))))
|
||||
|
||||
(defun doom--store-get (key location &optional default-value)
|
||||
(let* ((location-data (doom--store-init location))
|
||||
(data (gethash key location-data default-value)))
|
||||
(if (and (not (eq data default-value))
|
||||
(or (null (car data))
|
||||
(not (time-less-p (car data) (current-time)))))
|
||||
(cdr data)
|
||||
default-value)))
|
||||
|
||||
(defun doom--store-put (key value location &optional ttl)
|
||||
(puthash key (cons (if ttl (time-add (current-time) ttl)) value)
|
||||
(doom--store-init location))
|
||||
(doom--store-flush location))
|
||||
|
||||
(defun doom--store-flush (location)
|
||||
(unless doom--inhibit-flush
|
||||
(let ((file-name-handler-alist nil)
|
||||
(coding-system-for-write 'binary)
|
||||
(write-region-annotate-functions nil)
|
||||
(write-region-post-annotation-function nil)
|
||||
(data (doom--store-init location)))
|
||||
(make-directory doom-store-dir 'parents)
|
||||
(with-temp-file (expand-file-name location doom-store-dir)
|
||||
(prin1 data (current-buffer)))
|
||||
data)))
|
||||
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-store-get (key &optional location default-value)
|
||||
"Retrieve KEY from LOCATION (defaults to `doom-store-location').
|
||||
If it doesn't exist or has expired, DEFAULT_VALUE is returned."
|
||||
(doom--store-get key (or location doom-store-location) default-value))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-store-put (key value &optional ttl location)
|
||||
"Set KEY to VALUE in the store at LOCATION.
|
||||
KEY can be any lisp object that is comparable with `equal'. TTL is the duration
|
||||
(in seconds) after which this cache entry expires; if nil, no cache expiration.
|
||||
LOCATION is the super-key to store this cache item under. It defaults to
|
||||
`doom-store-location'."
|
||||
(doom--store-put key value (or location doom-store-location) ttl))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-store-rem (key &optional location)
|
||||
"Clear a cache LOCATION (defaults to `doom-store-location')."
|
||||
(let ((location (or location doom-store-location)))
|
||||
(remhash key (doom--store-init location))
|
||||
(let ((table (doom--store-init "default")))
|
||||
(remhash 'test table)
|
||||
table)
|
||||
(doom--store-flush location)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-store-member-p (key &optional location)
|
||||
"Return t if KEY in LOCATION exists.
|
||||
LOCATION defaults to `doom-store-location'."
|
||||
(let ((nil-value (format "--nilvalue%s--" (current-time))))
|
||||
(not (equal (doom-store-get key location nil-value)
|
||||
nil-value))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-store-clear (&optional location)
|
||||
"Clear the store at LOCATION (defaults to `doom-store-location')."
|
||||
(let* ((location (or location doom-store-location))
|
||||
(path (expand-file-name location doom-store-dir)))
|
||||
(remhash location doom--store-table)
|
||||
(when (file-exists-p path)
|
||||
(delete-file path)
|
||||
t)))
|
|
@ -0,0 +1,342 @@
|
|||
;;; 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: {[(
|
||||
|
||||
If INLINE is non-nil, only returns t if braces are on the same line, and
|
||||
whitespace is balanced on either side of the cursor.
|
||||
|
||||
If INLINE is nil, returns t if the opening and closing braces are on adjacent
|
||||
lines, above and below, with only whitespace in between."
|
||||
(when pair
|
||||
(let ((beg (plist-get pair :beg))
|
||||
(end (plist-get pair :end))
|
||||
(pt (point)))
|
||||
(when (and (> pt beg) (< pt end))
|
||||
(when-let* ((cl (plist-get pair :cl))
|
||||
(op (plist-get pair :op)))
|
||||
(and (not (string= op ""))
|
||||
(not (string= cl ""))
|
||||
(let ((nbeg (+ (length op) beg))
|
||||
(nend (- end (length cl))))
|
||||
(let ((content (buffer-substring-no-properties nbeg nend)))
|
||||
(and (string-match-p (format "[ %s]*" (if inline "" "\n")) content)
|
||||
(or (not balanced)
|
||||
(= (- pt nbeg) (- nend pt))))))))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-point-in-comment-p (&optional pos)
|
||||
"Return non-nil if POS is in a comment.
|
||||
POS defaults to the current position."
|
||||
(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'?
|
||||
(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)
|
||||
"Return non-nil if POS is in a string or comment."
|
||||
(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)
|
||||
;; Eglot, nox (a fork of eglot), and elpy implementations for
|
||||
;; `xref-backend-identifier-at-point' betray the documented purpose of
|
||||
;; the interface. Eglot/nox return a hardcoded string and elpy prepends
|
||||
;; the line number to the symbol.
|
||||
(if (memq (xref-find-backend) '(eglot elpy nox))
|
||||
(thing-at-point 'symbol 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
|
||||
|
||||
(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 (&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 "^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)
|
||||
(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 nil)
|
||||
;;;###autoload
|
||||
(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 ((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 ()
|
||||
"Inserts a tab character (or spaces x tab-width)."
|
||||
(interactive)
|
||||
(if indent-tabs-mode
|
||||
(insert "\t")
|
||||
(let* ((movement (% (current-column) tab-width))
|
||||
(spaces (if (= 0 movement) tab-width (- tab-width movement))))
|
||||
(insert (make-string spaces ? )))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/dumb-dedent ()
|
||||
"Dedents the current line."
|
||||
(interactive)
|
||||
(if indent-tabs-mode
|
||||
(call-interactively #'backward-delete-char)
|
||||
(unless (bolp)
|
||||
(save-excursion
|
||||
(when (> (current-column) (current-indentation))
|
||||
(back-to-indentation))
|
||||
(let ((movement (% (current-column) tab-width)))
|
||||
(delete-char
|
||||
(- (if (= 0 movement)
|
||||
tab-width
|
||||
(- tab-width movement)))))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/retab (arg &optional beg end)
|
||||
"Converts tabs-to-spaces or spaces-to-tabs within BEG and END (defaults to
|
||||
buffer start and end, to make indentation consistent. Which it does depends on
|
||||
the value of `indent-tab-mode'.
|
||||
|
||||
If ARG (universal argument) is non-nil, retab the current buffer using the
|
||||
opposite indentation style."
|
||||
(interactive "Pr")
|
||||
(unless (and beg end)
|
||||
(setq beg (point-min)
|
||||
end (point-max)))
|
||||
(let ((indent-tabs-mode (if arg (not indent-tabs-mode) indent-tabs-mode)))
|
||||
(if indent-tabs-mode
|
||||
(tabify beg end)
|
||||
(untabify beg end))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/delete-trailing-newlines ()
|
||||
"Trim trailing newlines.
|
||||
|
||||
Respects `require-final-newline'."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(goto-char (point-max))
|
||||
(delete-blank-lines)))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/dos2unix ()
|
||||
"Convert the current buffer to a Unix file encoding."
|
||||
(interactive)
|
||||
(set-buffer-file-coding-system 'undecided-unix nil))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/unix2dos ()
|
||||
"Convert the current buffer to a DOS file encoding."
|
||||
(interactive)
|
||||
(set-buffer-file-coding-system 'undecided-dos nil))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/toggle-indent-style ()
|
||||
"Switch between tabs and spaces indentation style in the current buffer."
|
||||
(interactive)
|
||||
(setq indent-tabs-mode (not indent-tabs-mode))
|
||||
(message "Indent style changed to %s" (if indent-tabs-mode "tabs" "spaces")))
|
||||
|
||||
(defvar editorconfig-lisp-use-default-indent)
|
||||
;;;###autoload
|
||||
(defun doom/set-indent-width (width)
|
||||
"Change the indentation size to WIDTH of the current buffer.
|
||||
|
||||
The effectiveness of this command is significantly improved if you have
|
||||
editorconfig or dtrt-indent installed."
|
||||
(interactive
|
||||
(list (if (integerp current-prefix-arg)
|
||||
current-prefix-arg
|
||||
(read-number "New indent size: "))))
|
||||
(setq tab-width width)
|
||||
(setq-local standard-indent width)
|
||||
(when (boundp 'evil-shift-width)
|
||||
(setq evil-shift-width width))
|
||||
(cond ((require 'editorconfig nil t)
|
||||
(let (editorconfig-lisp-use-default-indent)
|
||||
(editorconfig-set-indentation nil width)))
|
||||
((require 'dtrt-indent nil t)
|
||||
(when-let (var (nth 2 (assq major-mode dtrt-indent-hook-mapping-list)))
|
||||
(doom-log "Updated %s = %d" var width)
|
||||
(set var width))))
|
||||
(message "Changed indentation to %d" width))
|
||||
|
||||
|
||||
;;
|
||||
;;; Hooks
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-enable-delete-trailing-whitespace-h ()
|
||||
"Enables the automatic deletion of trailing whitespaces upon file save.
|
||||
|
||||
i.e. enables `ws-butler-mode' in the current buffer."
|
||||
(ws-butler-mode +1))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-disable-delete-trailing-whitespace-h ()
|
||||
"Disables the automatic deletion of trailing whitespaces upon file save.
|
||||
|
||||
i.e. disables `ws-butler-mode' in the current buffer."
|
||||
(ws-butler-mode -1))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-enable-show-trailing-whitespace-h ()
|
||||
"Enable `show-trailing-whitespace' in the current buffer."
|
||||
(setq-local show-trailing-whitespace t))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-disable-show-trailing-whitespace-h ()
|
||||
"Disable `show-trailing-whitespace' in the current buffer."
|
||||
(setq-local show-trailing-whitespace nil))
|
|
@ -0,0 +1,59 @@
|
|||
;;; core/autoload/themes.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defun doom--custom-theme-set-face (spec)
|
||||
(cond ((listp (car spec))
|
||||
(cl-loop for face in (car spec)
|
||||
collect
|
||||
(car (doom--custom-theme-set-face (cons face (cdr spec))))))
|
||||
((keywordp (cadr spec))
|
||||
`((,(car spec) ((t ,(cdr spec))))))
|
||||
(`((,(car spec) ,(cdr spec))))))
|
||||
|
||||
;;;###autoload
|
||||
(defconst doom-customize-theme-hook nil)
|
||||
|
||||
(add-hook! 'doom-load-theme-hook
|
||||
(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.
|
||||
|
||||
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-")))
|
||||
`(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 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))
|
||||
|
||||
(defvar doom--prefer-theme-elc)
|
||||
;;;###autoload
|
||||
(defun doom/reload-theme ()
|
||||
"Reload the current color theme."
|
||||
(interactive)
|
||||
(let ((theme (or (car-safe custom-enabled-themes) doom-theme)))
|
||||
(when theme
|
||||
(mapc #'disable-theme custom-enabled-themes))
|
||||
(load-theme doom-theme 'noconfirm)
|
||||
(doom/reload-font)))
|
|
@ -0,0 +1,281 @@
|
|||
;;; core/autoload/ui.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Public library
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-resize-window (window new-size &optional horizontal force-p)
|
||||
"Resize a window to NEW-SIZE. If HORIZONTAL, do it width-wise.
|
||||
If FORCE-P is omitted when `window-size-fixed' is non-nil, resizing will fail."
|
||||
(with-selected-window (or window (selected-window))
|
||||
(let ((window-size-fixed (unless force-p window-size-fixed)))
|
||||
(enlarge-window (- new-size (if horizontal (window-width) (window-height)))
|
||||
horizontal))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-quit-p (&optional prompt)
|
||||
"Prompt the user for confirmation when killing Emacs.
|
||||
|
||||
Returns t if it is safe to kill this session. Does not prompt if no real buffers
|
||||
are open."
|
||||
(or (not (ignore-errors (doom-real-buffer-list)))
|
||||
(yes-or-no-p (format "››› %s" (or prompt "Quit Emacs?")))
|
||||
(ignore (message "Aborted"))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Advice
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-recenter-a (&rest _)
|
||||
"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.
|
||||
|
||||
In interactive Emacs, this just inhibits messages from appearing in the
|
||||
minibuffer. They are still logged to *Messages*.
|
||||
|
||||
In tty Emacs, messages suppressed completely."
|
||||
(quiet! (apply orig-fn args)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Hooks
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-apply-ansi-color-to-compilation-buffer-h ()
|
||||
"Applies ansi codes to the compilation buffers. Meant for
|
||||
`compilation-filter-hook'."
|
||||
(with-silent-modifications
|
||||
(ansi-color-apply-on-region compilation-filter-start (point))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom-disable-show-paren-mode-h ()
|
||||
"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
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/toggle-line-numbers ()
|
||||
"Toggle line numbers.
|
||||
|
||||
Cycles through regular, relative and no line numbers. The order depends on what
|
||||
`display-line-numbers-type' is set to. If you're using Emacs 26+, and
|
||||
visual-line-mode is on, this skips relative and uses visual instead.
|
||||
|
||||
See `display-line-numbers' for what these values mean."
|
||||
(interactive)
|
||||
(defvar doom--line-number-style display-line-numbers-type)
|
||||
(let* ((styles `(t ,(if visual-line-mode 'visual 'relative) nil))
|
||||
(order (cons display-line-numbers-type (remq display-line-numbers-type styles)))
|
||||
(queue (memq doom--line-number-style order))
|
||||
(next (if (= (length queue) 1)
|
||||
(car order)
|
||||
(car (cdr queue)))))
|
||||
(setq doom--line-number-style next)
|
||||
(setq display-line-numbers next)
|
||||
(message "Switched to %s line numbers"
|
||||
(pcase next
|
||||
(`t "normal")
|
||||
(`nil "disabled")
|
||||
(_ (symbol-name next))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/delete-frame-with-prompt ()
|
||||
"Delete the current frame, but ask for confirmation if it isn't empty."
|
||||
(interactive)
|
||||
(if (cdr (frame-list))
|
||||
(when (doom-quit-p "Close frame?")
|
||||
(delete-frame))
|
||||
(save-buffers-kill-emacs)))
|
||||
|
||||
|
||||
(defun doom--enlargened-forget-last-wconf-h ()
|
||||
(set-frame-parameter nil 'doom--maximize-last-wconf nil)
|
||||
(set-frame-parameter nil 'doom--enlargen-last-wconf nil)
|
||||
(remove-hook 'doom-switch-window-hook #'doom--enlargened-forget-last-wconf-h))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/window-maximize-buffer (&optional arg)
|
||||
"Close other windows to focus on this one.
|
||||
|
||||
Activate again to undo this. If prefix ARG is non-nil, don't restore the last
|
||||
window configuration and re-maximize the current window. Alternatively, use
|
||||
`doom/window-enlargen'."
|
||||
(interactive "P")
|
||||
(let ((param 'doom--maximize-last-wconf))
|
||||
(cl-destructuring-bind (window . wconf)
|
||||
(or (frame-parameter nil param)
|
||||
(cons nil nil))
|
||||
(set-frame-parameter
|
||||
nil param
|
||||
(if (and (equal window (selected-window))
|
||||
(not arg)
|
||||
(null (cdr (cl-remove-if #'window-dedicated-p (window-list))))
|
||||
wconf)
|
||||
(ignore
|
||||
(let ((source-window (selected-window)))
|
||||
(set-window-configuration wconf)
|
||||
(when (window-live-p source-window)
|
||||
(select-window source-window))))
|
||||
(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 (cons (selected-window) (or wconf (current-window-configuration)))
|
||||
(delete-other-windows)
|
||||
(add-hook 'doom-switch-window-hook #'doom--enlargened-forget-last-wconf-h)))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/window-enlargen (&optional arg)
|
||||
"Enlargen the current window to focus on this one. Does not close other
|
||||
windows (unlike `doom/window-maximize-buffer'). Activate again to undo."
|
||||
(interactive "P")
|
||||
(let ((param 'doom--enlargen-last-wconf))
|
||||
(cl-destructuring-bind (window . wconf)
|
||||
(or (frame-parameter nil param)
|
||||
(cons nil nil))
|
||||
(set-frame-parameter
|
||||
nil param
|
||||
(if (and (equal window (selected-window))
|
||||
(not arg)
|
||||
wconf)
|
||||
(ignore
|
||||
(let ((source-window (selected-window)))
|
||||
(set-window-configuration wconf)
|
||||
(when (window-live-p source-window)
|
||||
(select-window source-window))))
|
||||
(prog1 (cons (selected-window) (or wconf (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)
|
||||
(window-resize-pixelwise nil)
|
||||
(frame-resize-pixelwise nil))
|
||||
(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))
|
||||
(add-hook 'doom-switch-window-hook #'doom--enlargened-forget-last-wconf-h)))))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/window-maximize-horizontally ()
|
||||
"Delete all windows to the left and right of the current window."
|
||||
(interactive)
|
||||
(require 'windmove)
|
||||
(save-excursion
|
||||
(while (ignore-errors (windmove-left)) (delete-window))
|
||||
(while (ignore-errors (windmove-right)) (delete-window))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/window-maximize-vertically ()
|
||||
"Delete all windows above and below the current window."
|
||||
(interactive)
|
||||
(require 'windmove)
|
||||
(save-excursion
|
||||
(while (ignore-errors (windmove-up)) (delete-window))
|
||||
(while (ignore-errors (windmove-down)) (delete-window))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/set-frame-opacity (opacity)
|
||||
"Interactively change the current frame's opacity.
|
||||
|
||||
OPACITY is an integer between 0 to 100, inclusive."
|
||||
(interactive
|
||||
(list (read-number "Opacity (0-100): "
|
||||
(or (frame-parameter nil 'alpha)
|
||||
100))))
|
||||
(set-frame-parameter nil 'alpha opacity))
|
||||
|
||||
(defvar doom--narrowed-base-buffer nil)
|
||||
;;;###autoload
|
||||
(defun doom/narrow-buffer-indirectly (beg end)
|
||||
"Restrict editing in this buffer to the current region, indirectly.
|
||||
|
||||
This recursively creates indirect clones of the current buffer so that the
|
||||
narrowing doesn't affect other windows displaying the same buffer. Call
|
||||
`doom/widen-indirectly-narrowed-buffer' to undo it (incrementally).
|
||||
|
||||
Inspired from http://demonastery.org/2013/04/emacs-evil-narrow-region/"
|
||||
(interactive
|
||||
(list (or (bound-and-true-p evil-visual-beginning) (region-beginning))
|
||||
(or (bound-and-true-p evil-visual-end) (region-end))))
|
||||
(unless (region-active-p)
|
||||
(setq beg (line-beginning-position)
|
||||
end (line-end-position)))
|
||||
(deactivate-mark)
|
||||
(let ((orig-buffer (current-buffer)))
|
||||
(with-current-buffer (switch-to-buffer (clone-indirect-buffer nil nil))
|
||||
(narrow-to-region beg end)
|
||||
(setq-local doom--narrowed-base-buffer orig-buffer))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/widen-indirectly-narrowed-buffer (&optional arg)
|
||||
"Widens narrowed buffers.
|
||||
|
||||
This command will incrementally kill indirect buffers (under the assumption they
|
||||
were created by `doom/narrow-buffer-indirectly') and switch to their base
|
||||
buffer.
|
||||
|
||||
If ARG, then kill all indirect buffers, return the base buffer and widen it.
|
||||
|
||||
If the current buffer is not an indirect buffer, it is `widen'ed."
|
||||
(interactive "P")
|
||||
(unless (buffer-narrowed-p)
|
||||
(user-error "Buffer isn't narrowed"))
|
||||
(let ((orig-buffer (current-buffer))
|
||||
(base-buffer doom--narrowed-base-buffer))
|
||||
(cond ((or (not base-buffer)
|
||||
(not (buffer-live-p base-buffer)))
|
||||
(widen))
|
||||
(arg
|
||||
(let ((buffer orig-buffer)
|
||||
(buffers-to-kill (list orig-buffer)))
|
||||
(while (setq buffer (buffer-local-value 'doom--narrowed-base-buffer buffer))
|
||||
(push buffer buffers-to-kill))
|
||||
(switch-to-buffer (buffer-base-buffer))
|
||||
(mapc #'kill-buffer (remove (current-buffer) buffers-to-kill))))
|
||||
((switch-to-buffer base-buffer)
|
||||
(kill-buffer orig-buffer)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun doom/toggle-narrow-buffer (beg end)
|
||||
"Narrow the buffer to BEG END. If narrowed, widen it."
|
||||
(interactive
|
||||
(list (or (bound-and-true-p evil-visual-beginning) (region-beginning))
|
||||
(or (bound-and-true-p evil-visual-end) (region-end))))
|
||||
(if (buffer-narrowed-p)
|
||||
(widen)
|
||||
(unless (region-active-p)
|
||||
(setq beg (line-beginning-position)
|
||||
end (line-end-position)))
|
||||
(narrow-to-region beg end)))
|
|
@ -0,0 +1,223 @@
|
|||
;;; core/cli/autoloads.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-autoloads-excluded-packages ()
|
||||
"What packages whose autoloads files 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.")
|
||||
|
||||
(defvar doom-autoloads-cached-vars
|
||||
'(doom-modules
|
||||
doom-disabled-packages
|
||||
comp-deferred-compilation-deny-list
|
||||
load-path
|
||||
auto-mode-alist
|
||||
interpreter-mode-alist
|
||||
Info-directory-list)
|
||||
"A list of variables to be cached in `doom-autoloads-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-autoloads-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 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"))))
|
||||
(cl-loop for var in doom-autoloads-cached-vars
|
||||
when (boundp var)
|
||||
collect `(set ',var ',(symbol-value var)))
|
||||
(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)
|
||||
(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 ; 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))))
|
||||
|
||||
(defun doom-autoloads--scan (files &optional literal)
|
||||
(require 'autoload)
|
||||
(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)))))))
|
|
@ -0,0 +1,202 @@
|
|||
;;; core/cli/byte-compile.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defcli! (compile c)
|
||||
((recompile-p ["-r" "--recompile"])
|
||||
(core-p ["-c" "--core"])
|
||||
(private-p ["-p" "--private"])
|
||||
(verbose-p ["-v" "--verbose"]))
|
||||
"Byte-compiles your config or selected modules.
|
||||
|
||||
compile [TARGETS...]
|
||||
compile :core :private lang/python
|
||||
compile feature lang
|
||||
|
||||
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 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."
|
||||
:bare t
|
||||
(doom-clean-byte-compiled-files))
|
||||
|
||||
|
||||
;;
|
||||
;; Helpers
|
||||
|
||||
(defun doom--byte-compile-ignore-file-p (path)
|
||||
(let ((filename (file-name-nondirectory path)))
|
||||
(or (not (equal (file-name-extension path) "el"))
|
||||
(member filename (list "packages.el" "doctor.el"))
|
||||
(string-prefix-p "." filename)
|
||||
(string-prefix-p "test-" filename)
|
||||
(string-prefix-p "flycheck_" filename)
|
||||
(string-suffix-p ".example.el" filename))))
|
||||
|
||||
(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.
|
||||
|
||||
WARNING: byte-compilation yields marginal gains and makes debugging new issues
|
||||
difficult. It is recommended you don't use it unless you understand the
|
||||
reprecussions.
|
||||
|
||||
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)
|
||||
(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 ((load-prefer-newer t)
|
||||
(noninteractive t)
|
||||
doom-interactive-p)
|
||||
(doom-initialize 'force)
|
||||
(quiet! (doom-initialize-packages))))
|
||||
|
||||
(if (null targets)
|
||||
(print! (info "No targets to %scompile" (if recompile-p "re" "")))
|
||||
(print! (start "%scompiling your config...")
|
||||
(if recompile-p "Re" "Byte-"))
|
||||
|
||||
(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)))
|
||||
|
||||
(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")
|
||||
i total-modules
|
||||
(if-let (m (caar module-files))
|
||||
(format "%s %s module..." m (cdar module-files))
|
||||
(format "%d stand alone elisp files..."
|
||||
(length (cdr module-files))))
|
||||
(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
|
||||
module. This does not include your byte-compiled, third party packages.'"
|
||||
(require 'core-modules)
|
||||
(print! (start "Cleaning .elc files"))
|
||||
(print-group!
|
||||
(cl-loop with default-directory = doom-emacs-dir
|
||||
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)
|
||||
(doom-files-in doom-core-dir :match "\\.elc$")
|
||||
(doom-files-in doom-modules-dirs :match "\\.elc$" :depth 4))
|
||||
if (file-exists-p path)
|
||||
do (delete-file path)
|
||||
and do (print! (success "\033[KDeleted %s%s") (relpath path) esc)
|
||||
and do (cl-incf success)
|
||||
finally do
|
||||
(print! (if (> success 0)
|
||||
(success "\033[K%d elc files deleted" success)
|
||||
(info "\033[KNo elc files to clean"))))
|
||||
t))
|
|
@ -0,0 +1,30 @@
|
|||
;;; 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"))
|
||||
"Output system info in markdown for bug reports."
|
||||
(pcase format
|
||||
("--json"
|
||||
(require 'json)
|
||||
(with-temp-buffer
|
||||
(insert (json-encode (doom-info)))
|
||||
(json-pretty-print-buffer)
|
||||
(print! (buffer-string))))
|
||||
("--lisp"
|
||||
(doom/info 'raw))
|
||||
(`nil
|
||||
(doom/info))
|
||||
(_
|
||||
(user-error "I don't understand %S. Did you mean --json, --md/--markdown or --lisp?"
|
||||
format)))
|
||||
nil)
|
||||
|
||||
(defcli! (version v) ()
|
||||
"Show version information for Doom & Emacs."
|
||||
(doom/version)
|
||||
nil)
|
|
@ -0,0 +1,225 @@
|
|||
;;; core/cli/doctor.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-warnings ())
|
||||
(defvar doom-errors ())
|
||||
|
||||
;;; Helpers
|
||||
(defun elc-check-dir (dir)
|
||||
(dolist (file (directory-files-recursively dir "\\.elc$"))
|
||||
(when (file-newer-than-file-p (concat (file-name-sans-extension file) ".el")
|
||||
file)
|
||||
(warn! "%s is out-of-date" (abbreviate-file-name file)))))
|
||||
|
||||
(defmacro assert! (condition message &rest args)
|
||||
`(unless ,condition
|
||||
(error! ,message ,@args)))
|
||||
|
||||
|
||||
;;; Logging
|
||||
(defmacro error! (&rest args)
|
||||
`(progn (unless inhibit-message (print! (error ,@args)))
|
||||
(push (format! (error ,@args)) doom-errors)))
|
||||
(defmacro warn! (&rest args)
|
||||
`(progn (unless inhibit-message (print! (warn ,@args)))
|
||||
(push (format! (warn ,@args)) doom-warnings)))
|
||||
(defmacro success! (&rest args)
|
||||
`(print! (green ,@args)))
|
||||
(defmacro section! (&rest args)
|
||||
`(print! (bold (blue ,@args))))
|
||||
|
||||
(defmacro explain! (&rest args)
|
||||
`(print-group! (print! (autofill ,@args))))
|
||||
|
||||
|
||||
;;
|
||||
;;; CLI commands
|
||||
|
||||
(defcli! (doctor doc) ()
|
||||
"Diagnoses common issues on your system.
|
||||
|
||||
The Doom doctor is essentially one big, self-contained elisp shell script that
|
||||
uses a series of simple heuristics to diagnose common issues on your system.
|
||||
Issues that could intefere with Doom Emacs.
|
||||
|
||||
Doom modules may optionally have a doctor.el file to run their own heuristics
|
||||
in."
|
||||
:bare t
|
||||
(print! "The doctor will see you now...\n")
|
||||
|
||||
;; REVIEW Refactor me
|
||||
(print! (start "Checking your Emacs version..."))
|
||||
(cond
|
||||
(EMACS28+
|
||||
(warn! (concat "Emacs %s detected. Doom should support this version, but be prepared for "
|
||||
"Emacs updates causing breakages.")
|
||||
emacs-version))
|
||||
((= emacs-major-version 26)
|
||||
(warn! (concat "Emacs %s detected. Doom is dropping Emacs 26.x support very soon. Consider "
|
||||
"upgrading to Emacs 27.x.")
|
||||
emacs-version)))
|
||||
|
||||
(print! (start "Checking for Emacs config conflicts..."))
|
||||
(when (file-exists-p "~/.emacs")
|
||||
(warn! "Detected an ~/.emacs file, which may prevent Doom from loading")
|
||||
(explain! "If Emacs finds an ~/.emacs file, it will ignore ~/.emacs.d, where Doom is "
|
||||
"typically installed. If you're seeing a vanilla Emacs splash screen, this "
|
||||
"may explain why. If you use Chemacs, you may ignore this warning."))
|
||||
|
||||
(when EMACS27+
|
||||
(print! (start "Checking for great Emacs features..."))
|
||||
(unless (and (functionp 'json-serialize)
|
||||
(string-match-p "\\_<JSON\\_>" system-configuration-features))
|
||||
(warn! "Emacs was not built with native JSON support")
|
||||
(explain! "Users will see a substantial performance gain by building Emacs with "
|
||||
"jansson support (i.e. a native JSON library), particularly LSP users. "
|
||||
"You must install a prebuilt Emacs binary with this included, or compile "
|
||||
"Emacs with the --with-json option.")))
|
||||
|
||||
(print! (start "Checking for private config conflicts..."))
|
||||
(let ((xdg-dir (concat (or (getenv "XDG_CONFIG_HOME")
|
||||
"~/.config")
|
||||
"/doom/"))
|
||||
(doom-dir (or (getenv "DOOMDIR")
|
||||
"~/.doom.d/")))
|
||||
(when (and (not (file-equal-p xdg-dir doom-dir))
|
||||
(file-directory-p xdg-dir)
|
||||
(file-directory-p doom-dir))
|
||||
(print! (warn "Detected two private configs, in %s and %s")
|
||||
(abbreviate-file-name xdg-dir)
|
||||
doom-dir)
|
||||
(explain! "The second directory will be ignored, as it has lower precedence.")))
|
||||
|
||||
(print! (start "Checking for stale elc files..."))
|
||||
(elc-check-dir user-emacs-directory)
|
||||
|
||||
(print! (start "Checking Doom Emacs..."))
|
||||
(condition-case-unless-debug ex
|
||||
(print-group!
|
||||
(let ((doom-interactive-p 'doctor))
|
||||
(doom-initialize 'force)
|
||||
(doom-initialize-modules))
|
||||
|
||||
(print! (success "Initialized Doom Emacs %s") doom-version)
|
||||
(print!
|
||||
(if (hash-table-p doom-modules)
|
||||
(success "Detected %d modules" (hash-table-count doom-modules))
|
||||
(warn "Failed to load any modules. Do you have an private init.el?")))
|
||||
|
||||
(print! (success "Detected %d packages") (length doom-packages))
|
||||
|
||||
(print! (start "Checking Doom core for irregularities..."))
|
||||
(print-group!
|
||||
;; Check for oversized problem files in cache that may cause unusual/tremendous
|
||||
;; delays or freezing. This shouldn't happen often.
|
||||
(dolist (file (list "savehist" "projectile.cache"))
|
||||
(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 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"))
|
||||
|
||||
(require 'projectile)
|
||||
(when (projectile-project-root "~")
|
||||
(warn! "Your $HOME is recognized as a project root")
|
||||
(explain! "Doom will disable bottom-up root search, which may reduce the accuracy of project\n"
|
||||
"detection."))
|
||||
|
||||
;; There should only be one
|
||||
(when (and (file-equal-p doom-private-dir "~/.config/doom")
|
||||
(file-directory-p "~/.doom.d"))
|
||||
(print! (warn "Both %S and '~/.doom.d' exist on your system")
|
||||
(path doom-private-dir))
|
||||
(explain! "Doom will only load one of these (~/.config/doom takes precedence). Possessing\n"
|
||||
"both is rarely intentional; you should one or the other."))
|
||||
|
||||
;; Check for fonts
|
||||
(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")
|
||||
"~/.local/share")
|
||||
"/fonts/"))
|
||||
(`darwin "~/Library/Fonts/"))
|
||||
(require 'all-the-icons nil t))
|
||||
(with-temp-buffer
|
||||
(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)
|
||||
(print-group!
|
||||
(elc-check-dir doom-private-dir)))
|
||||
|
||||
(when doom-modules
|
||||
(print! (start "Checking your enabled modules..."))
|
||||
(advice-add #'require :around #'doom-shut-up-a)
|
||||
(maphash (lambda (key plist)
|
||||
(let (doom-local-errors
|
||||
doom-local-warnings)
|
||||
(let (doom-errors
|
||||
doom-warnings)
|
||||
(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 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 "Missing emacs package: %S") name))
|
||||
(let ((inhibit-message t))
|
||||
(load doctor-file 'noerror 'nomessage)))
|
||||
(file-missing (error! "%s" (error-message-string ex)))
|
||||
(error (error! "Syntax error: %s" ex)))
|
||||
(when (or doom-errors doom-warnings)
|
||||
(print-group!
|
||||
(print! (start (bold "%s %s")) (car key) (cdr key))
|
||||
(print! "%s" (string-join (append doom-errors doom-warnings) "\n")))
|
||||
(setq doom-local-errors doom-errors
|
||||
doom-local-warnings doom-warnings)))
|
||||
(appendq! doom-errors doom-local-errors)
|
||||
(appendq! doom-warnings doom-local-warnings)))
|
||||
doom-modules)))
|
||||
(error
|
||||
(warn! "Attempt to load DOOM failed\n %s\n"
|
||||
(or (cdr-safe ex) (car ex)))
|
||||
(setq doom-modules nil)))
|
||||
|
||||
;; Final report
|
||||
(message "")
|
||||
(dolist (msg (list (list doom-errors "error" 'red)
|
||||
(list doom-warnings "warning" 'yellow)))
|
||||
(when (car msg)
|
||||
(print! (color (nth 2 msg)
|
||||
(if (cdr msg)
|
||||
"There are %d %ss!"
|
||||
"There is %d %s!")
|
||||
(length (car msg)) (nth 1 msg)))))
|
||||
(unless (or doom-errors doom-warnings)
|
||||
(success! "Everything seems fine, happy Emacs'ing!"))
|
||||
t)
|
|
@ -0,0 +1,137 @@
|
|||
;;; core/cli/env.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defcli! env
|
||||
((allow ["-a" "--allow" regexp] "An additive envvar whitelist regexp")
|
||||
(reject ["-r" "--reject" regexp] "An additive envvar blacklist regexp")
|
||||
(allow-only ["-A" regexp] "Blacklist everything but REGEXP")
|
||||
(reject-only ["-R" regexp] "Whitelist everything but 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 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 sync'.
|
||||
However, 'doom sync' will only regenerate this file if it exists.
|
||||
|
||||
Why this over exec-path-from-shell?
|
||||
|
||||
1. `exec-path-from-shell' spawns (at least) one process at startup to scrape
|
||||
your shell environment. This can be arbitrarily slow depending on the
|
||||
user's shell configuration. A single program (like pyenv or nvm) or config
|
||||
framework (like oh-my-zsh) could undo all of Doom's startup optimizations
|
||||
in one fell swoop.
|
||||
|
||||
2. `exec-path-from-shell' only scrapes some state from your shell. You have to
|
||||
be proactive in order to get it to capture all the envvars relevant to your
|
||||
development environment.
|
||||
|
||||
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 you least want to deal with it."
|
||||
(let ((env-file (expand-file-name (or outputfile doom-env-file))))
|
||||
(if (null clear-p)
|
||||
(doom-cli-reload-env-file
|
||||
'force env-file
|
||||
(append (if reject-only (list "."))
|
||||
(delq nil (list allow allow-only)))
|
||||
(append (if allow-only (list "."))
|
||||
(delq nil (list reject reject-only))))
|
||||
(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-blacklist
|
||||
'(;; State that may be problematic if overwritten
|
||||
"^HOME$" "^\\(OLD\\)?PWD$" "^SHLVL$" "^PS1$" "^R?PROMPT$" "^TERM$" "^USER$"
|
||||
;; X server or services' variables
|
||||
"^DISPLAY$" "^DBUS_SESSION_BUS_ADDRESS$"
|
||||
;; ssh and gpg variables (likely to become stale)
|
||||
"^SSH_\\(AUTH_SOCK\\|AGENT_PID\\)$" "^\\(SSH\\|GPG\\)_TTY$"
|
||||
"^GPG_AGENT_INFO$"
|
||||
;; Internal Doom envvars
|
||||
"^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'.")
|
||||
|
||||
(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))
|
||||
(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-unix)
|
||||
(print! (start "%s envvars file at %S")
|
||||
(if (file-exists-p env-file)
|
||||
"Regenerating"
|
||||
"Generating")
|
||||
(path env-file))
|
||||
(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; coding: utf-8-unix -*-\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! (debug "Ignoring %s") env)
|
||||
(print! (debug "Whitelisted %s") env)
|
||||
(insert env "\0\n"))
|
||||
(insert env "\0\n")))
|
||||
(print! (success "Successfully generated %S")
|
||||
(path env-file))
|
||||
t)))))
|
|
@ -0,0 +1,102 @@
|
|||
;;; core/cli/help.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defun doom--cli-print-signature (cli)
|
||||
(print! (bold "Usage: doom %s%s%s")
|
||||
(if (doom-cli-internal-p cli)
|
||||
""
|
||||
(concat (doom-cli-name cli) " "))
|
||||
(if-let* ((optlist (doom-cli-optlist cli))
|
||||
(flags (cl-loop for opt in optlist
|
||||
append (doom-cli-option-flags opt)))
|
||||
(fn (doom-partial #'string-prefix-p "--")))
|
||||
(concat (when-let (short-flags (cl-remove-if fn flags))
|
||||
;; TODO Show arguments of short flags
|
||||
(format "[-%s]"
|
||||
(string-join (mapcar (doom-rpartial #'substring 1 nil) short-flags)
|
||||
"")))
|
||||
;; TODO Show long flags
|
||||
;; (when-let (long-flags (cl-remove-if-not fn flags))
|
||||
;; (concat " " (string-join long-flags " ")))
|
||||
" ")
|
||||
"")
|
||||
(if-let (arglist (doom-cli-arglist cli))
|
||||
(string-join (append (cl-loop for arg in arglist
|
||||
until (memq arg cl--lambda-list-keywords)
|
||||
collect (upcase (symbol-name arg)))
|
||||
(cl-loop for arg in (cdr (memq '&optional arglist))
|
||||
until (memq arg cl--lambda-list-keywords)
|
||||
collect (format "[%s]" (upcase (symbol-name arg)))))
|
||||
" ")
|
||||
""))
|
||||
(when-let (aliases (doom-cli-aliases cli))
|
||||
(print! "Aliases: %s" (string-join aliases ", "))))
|
||||
|
||||
(defun doom--cli-print-desc (cli &optional short)
|
||||
(print! "%s"
|
||||
(if short
|
||||
(car (split-string (doom-cli-desc cli) "\n"))
|
||||
(doom-cli-desc cli))))
|
||||
|
||||
(defun doom--cli-print-short-desc (cli)
|
||||
(doom--cli-print-desc cli 'short))
|
||||
|
||||
(defun doom--cli-print-options (cli)
|
||||
(when-let (optlist (doom-cli-optlist cli))
|
||||
(print! (bold "Options:"))
|
||||
(print-group!
|
||||
(cl-loop for opt in optlist
|
||||
for desc = (doom-cli-option-desc opt)
|
||||
for args = (doom-cli-option-args opt)
|
||||
for flagstr = (string-join (doom-cli-option-flags opt) ", ")
|
||||
do
|
||||
;; TODO Adjust columns dynamically
|
||||
(print! "%-18s"
|
||||
(concat flagstr
|
||||
(when-let (arg (car args))
|
||||
(concat " " (upcase (symbol-name arg))))))
|
||||
(print-group!
|
||||
(print! (autofill "%s") desc))))))
|
||||
|
||||
|
||||
(defun doom--cli-print (cli)
|
||||
(doom--cli-print-signature cli)
|
||||
(terpri)
|
||||
(doom--cli-print-desc cli)
|
||||
(terpri)
|
||||
(doom--cli-print-options cli))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defcli! (help h) (&optional command)
|
||||
"Describe a command or list them all."
|
||||
:bare t
|
||||
(if command
|
||||
(doom--cli-print (doom-cli-get (intern command)))
|
||||
(doom--cli-print (doom-cli-get :doom))
|
||||
(terpri)
|
||||
(print! (bold "Commands:"))
|
||||
(print-group!
|
||||
(dolist (group (seq-group-by (lambda (cli)
|
||||
(plist-get (doom-cli-plist cli) :group))
|
||||
(cl-loop for name being the hash-keys of doom--cli-commands
|
||||
for cli = (gethash name doom--cli-commands)
|
||||
if (and (doom-cli-p cli)
|
||||
(not (doom-cli-internal-p cli))
|
||||
(not (plist-get (doom-cli-plist cli) :hidden)))
|
||||
collect cli)))
|
||||
(if (null (car group))
|
||||
(dolist (cli (cdr group))
|
||||
(print! "%-16s %s"
|
||||
(doom-cli-name cli)
|
||||
(car (split-string (doom-cli-desc cli) "\n"))))
|
||||
(print! "%-26s %s"
|
||||
(bold (concat (car group) ":"))
|
||||
(gethash (car group) doom--cli-groups))
|
||||
(print-group!
|
||||
(dolist (cli (cdr group))
|
||||
(print! "%-16s %s"
|
||||
(doom-cli-name cli)
|
||||
(car (split-string (doom-cli-desc cli) "\n"))))))
|
||||
(terpri)))))
|
|
@ -0,0 +1,100 @@
|
|||
;;; core/cli/install.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defcli! (install i)
|
||||
((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"))
|
||||
"Installs and sets up Doom Emacs for the first time.
|
||||
|
||||
This command does the following:
|
||||
|
||||
1. Creates DOOMDIR at ~/.doom.d,
|
||||
2. Copies ~/.emacs.d/init.example.el to $DOOMDIR/init.el (if it doesn't exist),
|
||||
3. Creates dummy files for $DOOMDIR/{config,packages}.el,
|
||||
4. Prompts you to generate an envvar file (same as 'doom env'),
|
||||
5. Installs any dependencies of enabled modules (specified by $DOOMDIR/init.el),
|
||||
6. And prompts to install all-the-icons' fonts
|
||||
|
||||
This command is idempotent and safe to reuse.
|
||||
|
||||
The location of DOOMDIR can be changed with the -p option, or by setting the
|
||||
DOOMDIR environment variable. e.g.
|
||||
|
||||
doom -p ~/.config/doom install
|
||||
DOOMDIR=~/.config/doom doom install"
|
||||
(print! (green "Installing Doom Emacs!\n"))
|
||||
(let ((default-directory (doom-path "~")))
|
||||
;; Create `doom-private-dir'
|
||||
(if noconfig-p
|
||||
(print! (warn "Not copying private config template, as requested"))
|
||||
;; Create DOOMDIR in ~/.config/doom if ~/.config/emacs exists.
|
||||
(when (and (not (file-directory-p doom-private-dir))
|
||||
(not (getenv "DOOMDIR")))
|
||||
(let ((xdg-config-dir (or (getenv "XDG_CONFIG_HOME") "~/.config")))
|
||||
(when (file-in-directory-p doom-emacs-dir xdg-config-dir)
|
||||
(setq doom-private-dir (expand-file-name "doom/" xdg-config-dir)))))
|
||||
(print! (start "Creating %s") (relpath doom-private-dir))
|
||||
(make-directory doom-private-dir 'parents)
|
||||
(print-group!
|
||||
(print! (success "Created %s") (relpath doom-private-dir)))
|
||||
|
||||
;; Create init.el, config.el & packages.el
|
||||
(mapc (lambda (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)
|
||||
(insert-file-contents template))
|
||||
(print! (success "Done!")))))
|
||||
`(("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-modules 'force 'no-config)
|
||||
|
||||
;; Ask if user would like an envvar file generated
|
||||
(if noenv-p
|
||||
(print! (warn "Not generating envvars file, as requested"))
|
||||
(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 envvar file? (see `doom help env` for details)"))
|
||||
(doom-cli-reload-env-file 'force-p))))
|
||||
|
||||
;; Install Doom packages
|
||||
(if noinstall-p
|
||||
(print! (warn "Not installing plugins, as requested"))
|
||||
(print! "Installing plugins")
|
||||
(doom-cli-packages-install))
|
||||
|
||||
(print! "Regenerating autoloads files")
|
||||
(doom-autoloads-reload)
|
||||
|
||||
(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
|
||||
(insert-file-contents (doom-glob doom-core-dir "templates/QUICKSTART_INTRO"))
|
||||
(print! "%s" (buffer-string)))))
|
|
@ -0,0 +1,610 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/cli/packages.el
|
||||
|
||||
(defcli! (update u) (&rest _)
|
||||
"This command was removed."
|
||||
:hidden t
|
||||
(print! (error "This command has been removed.\n"))
|
||||
(print-group!
|
||||
(print! "To update Doom run 'doom upgrade'. To only update packages run 'doom sync -u'."))
|
||||
nil)
|
||||
|
||||
(defcli! (build b)
|
||||
((rebuild-p ["-r"] "Only rebuild packages that need rebuilding"))
|
||||
"Byte-compiles & symlinks installed packages.
|
||||
|
||||
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-autoloads-reload))
|
||||
t)
|
||||
|
||||
(defcli! (purge p)
|
||||
((nobuilds-p ["-b" "--no-builds"] "Don't purge unneeded (built) packages")
|
||||
(noelpa-p ["-p" "--no-elpa"] "Don't purge ELPA packages")
|
||||
(norepos-p ["-r" "--no-repos"] "Don't purge unused straight repos")
|
||||
(regraft-p ["-g" "--regraft"] "Regraft git repos (ie. compact them)"))
|
||||
"Deletes orphaned packages & repos, and compacts them.
|
||||
|
||||
Purges all installed ELPA packages (as they are considered temporary). Purges
|
||||
all orphaned package repos and builds. If -g/--regraft is supplied, the git
|
||||
repos among them will be regrafted and compacted to ensure they are as small as
|
||||
possible.
|
||||
|
||||
It is a good idea to occasionally run this doom purge -g to ensure your package
|
||||
list remains lean."
|
||||
(straight-check-all)
|
||||
(when (doom-cli-packages-purge
|
||||
(not noelpa-p)
|
||||
(not norepos-p)
|
||||
(not nobuilds-p)
|
||||
regraft-p)
|
||||
(doom-autoloads-reload))
|
||||
t)
|
||||
|
||||
;; (defcli! rollback () ; TODO doom rollback
|
||||
;; "<Not implemented yet>"
|
||||
;; (user-error "Not implemented yet, sorry!"))
|
||||
|
||||
|
||||
;;
|
||||
;;; 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)
|
||||
(when-let*
|
||||
((status (straight--call
|
||||
"git" "log" "--oneline" "--no-merges"
|
||||
"-n" "26" end-ref (concat "^" (regexp-quote start-ref))))
|
||||
(output (string-trim-right (straight--process-get-output)))
|
||||
(lines (split-string output "\n")))
|
||||
(if (> (length lines) 25)
|
||||
(concat (string-join (butlast lines 1) "\n") "\n[...]")
|
||||
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)))
|
||||
|
||||
(defvar doom--eln-output-expected nil)
|
||||
|
||||
(defvar doom--eln-output-path (car (bound-and-true-p comp-eln-load-path)))
|
||||
|
||||
(defun doom--eln-file-name (file)
|
||||
"Return the short .eln file name corresponding to `file'."
|
||||
(concat comp-native-version-dir "/"
|
||||
(file-name-nondirectory
|
||||
(comp-el-to-eln-filename file))))
|
||||
|
||||
(defun doom--eln-output-file (eln-name)
|
||||
"Return the expected .eln file corresponding to `eln-name'."
|
||||
(concat doom--eln-output-path eln-name))
|
||||
|
||||
(defun doom--eln-error-file (eln-name)
|
||||
"Return the expected .error file corresponding to `eln-name'."
|
||||
(concat doom--eln-output-path eln-name ".error"))
|
||||
|
||||
(defun doom--find-eln-file (eln-name)
|
||||
"Find `eln-name' on the `comp-eln-load-path'."
|
||||
(cl-some (lambda (eln-path)
|
||||
(let ((file (concat eln-path eln-name)))
|
||||
(when (file-exists-p file)
|
||||
file)))
|
||||
comp-eln-load-path))
|
||||
|
||||
(defun doom--elc-file-outdated-p (file)
|
||||
"Check whether the corresponding .elc for `file' is outdated."
|
||||
(let ((elc-file (byte-compile-dest-file file)))
|
||||
;; NOTE Ignore missing elc files, they could be missing due to
|
||||
;; `no-byte-compile'. Rebuilding unnecessarily is expensive.
|
||||
(when (and (file-exists-p elc-file)
|
||||
(file-newer-than-file-p file elc-file))
|
||||
(doom-log "%s is newer than %s" file elc-file)
|
||||
t)))
|
||||
|
||||
(defun doom--eln-file-outdated-p (file)
|
||||
"Check whether the corresponding .eln for `file' is outdated."
|
||||
(let* ((eln-name (doom--eln-file-name file))
|
||||
(eln-file (doom--find-eln-file eln-name))
|
||||
(error-file (doom--eln-error-file eln-name)))
|
||||
(cond (eln-file
|
||||
(when (file-newer-than-file-p file eln-file)
|
||||
(doom-log "%s is newer than %s" file eln-file)
|
||||
t))
|
||||
((file-exists-p error-file)
|
||||
(when (file-newer-than-file-p file error-file)
|
||||
(doom-log "%s is newer than %s" file error-file)
|
||||
t))
|
||||
(t
|
||||
(doom-log "%s doesn't exist" eln-name)
|
||||
t))))
|
||||
|
||||
(defun doom--native-compile-done-h (file)
|
||||
"Callback fired when an item has finished async compilation."
|
||||
(when file
|
||||
(let* ((eln-name (doom--eln-file-name file))
|
||||
(eln-file (doom--eln-output-file eln-name))
|
||||
(error-file (doom--eln-error-file eln-name)))
|
||||
(if (file-exists-p eln-file)
|
||||
(doom-log "Compiled %s" eln-file)
|
||||
(make-directory (file-name-directory error-file) 'parents)
|
||||
(write-region "" nil error-file)
|
||||
(doom-log "Wrote %s" error-file)))))
|
||||
|
||||
(defun doom--native-compile-jobs ()
|
||||
"How many async native compilation jobs are queued or in-progress."
|
||||
(if (featurep 'comp)
|
||||
(+ (length comp-files-queue)
|
||||
(comp-async-runnings))
|
||||
0))
|
||||
|
||||
(defun doom--wait-for-compile-jobs ()
|
||||
"Wait for all pending async native compilation jobs."
|
||||
(cl-loop for pending = (doom--native-compile-jobs)
|
||||
with previous = 0
|
||||
while (not (zerop pending))
|
||||
if (/= previous pending) do
|
||||
(print! (info "\033[KWaiting for %d async jobs...\033[1A" pending))
|
||||
(setq previous pending)
|
||||
else do
|
||||
(let ((inhibit-message t))
|
||||
(sleep-for 0.1))))
|
||||
|
||||
(defun doom--write-missing-eln-errors ()
|
||||
"Write .error files for any expected .eln files that are missing."
|
||||
(cl-loop for file in doom--eln-output-expected
|
||||
for eln-name = (doom--eln-file-name file)
|
||||
for eln-file = (doom--eln-output-file eln-name)
|
||||
for error-file = (doom--eln-error-file eln-name)
|
||||
unless (or (file-exists-p eln-file)
|
||||
(file-newer-than-file-p error-file file))
|
||||
do (make-directory (file-name-directory error-file) 'parents)
|
||||
(write-region "" nil error-file)
|
||||
(doom-log "Wrote %s" error-file))
|
||||
(setq doom--eln-output-expected nil))
|
||||
|
||||
(defun doom--compile-site-packages ()
|
||||
"Queue async compilation for all non-doom Elisp files."
|
||||
(when (featurep 'comp)
|
||||
(cl-loop with paths = (cl-loop for path in load-path
|
||||
unless (string-prefix-p doom-local-dir path)
|
||||
collect path)
|
||||
for file in (doom-files-in paths :match "\\.el\\(?:\\.gz\\)?$")
|
||||
if (and (file-exists-p (byte-compile-dest-file file))
|
||||
(not (doom--find-eln-file (doom--eln-file-name file)))
|
||||
(not (cl-some (lambda (re)
|
||||
(string-match-p re file))
|
||||
comp-deferred-compilation-deny-list))) do
|
||||
(doom-log "Compiling %s" file)
|
||||
(native-compile-async file))))
|
||||
|
||||
(defun doom--bootstrap-trampolines ()
|
||||
"Build the trampolines we need to prevent hanging."
|
||||
(when (featurep 'comp)
|
||||
;; HACK The following list was obtained by running 'doom build', waiting for
|
||||
;; it to hang, then checking the eln-cache for trampolines. We
|
||||
;; simulate running 'doom build' twice by compiling the trampolines
|
||||
;; then restarting.
|
||||
(let (restart)
|
||||
(dolist (f '(abort-recursive-edit
|
||||
describe-buffer-bindings
|
||||
execute-kbd-macro
|
||||
handle-switch-frame
|
||||
load
|
||||
make-indirect-buffer
|
||||
make-process
|
||||
message
|
||||
read-char
|
||||
read-key-sequence
|
||||
select-window
|
||||
set-window-buffer
|
||||
top-level
|
||||
use-global-map
|
||||
use-local-map
|
||||
write-region))
|
||||
(unless (doom--find-eln-file
|
||||
(concat comp-native-version-dir "/"
|
||||
(comp-trampoline-filename f)))
|
||||
(print! (info "Compiling trampoline for %s") f)
|
||||
(comp-trampoline-compile f)
|
||||
(setq restart t)))
|
||||
(when restart
|
||||
(throw 'exit :restart)))))
|
||||
|
||||
|
||||
(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."
|
||||
(doom--bootstrap-trampolines)
|
||||
(doom-initialize-packages)
|
||||
(print! (start "Installing packages..."))
|
||||
(let ((pinned (doom-package-pinned-list)))
|
||||
(print-group!
|
||||
(add-hook 'comp-async-cu-done-hook #'doom--native-compile-done-h)
|
||||
(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: %s") pkg commit)))
|
||||
straight-use-package-pre-build-functions)))
|
||||
(straight-use-package (intern package))
|
||||
;; HACK Line encoding issues can plague repos with dirty
|
||||
;; worktree prompts when updating packages or "Local
|
||||
;; variables entry is missing the suffix" errors when
|
||||
;; installing them (see hlissner/doom-emacs#2637), so
|
||||
;; have git handle conversion by force.
|
||||
(when (and IS-WINDOWS (stringp local-repo))
|
||||
(let ((default-directory (straight--repos-dir local-repo)))
|
||||
(when (file-in-directory-p default-directory straight-base-dir)
|
||||
(straight--call "git" "config" "core.autocrlf" "true")))))
|
||||
(error
|
||||
(signal 'doom-package-error (list package e))))))
|
||||
(progn
|
||||
(doom--compile-site-packages)
|
||||
(doom--wait-for-compile-jobs)
|
||||
(doom--write-missing-eln-errors)
|
||||
(print! (success "\033[KInstalled %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--bootstrap-trampolines)
|
||||
(doom-initialize-packages)
|
||||
(print! (start "(Re)building %spackages...") (if force-p "all " ""))
|
||||
(print-group!
|
||||
(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)))
|
||||
(add-hook 'comp-async-cu-done-hook #'doom--native-compile-done-h)
|
||||
(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))
|
||||
(build (plist-get recipe :build))
|
||||
(want-byte-compile
|
||||
(or (eq build t)
|
||||
(memq 'compile build)))
|
||||
(want-native-compile
|
||||
(and (or (eq build t)
|
||||
(memq 'native-compile build))
|
||||
(require 'comp nil t))))
|
||||
(when (eq (car build) :not)
|
||||
(setq want-byte-compile (not want-byte-compile)
|
||||
want-native-compile (not want-native-compile)))
|
||||
(and (or want-byte-compile want-native-compile)
|
||||
(or (file-newer-than-file-p repo-dir build-dir)
|
||||
(file-exists-p (straight--modified-dir (or local-repo package)))
|
||||
(cl-loop with outdated = nil
|
||||
for file in (doom-files-in build-dir :match "\\.el$" :full t)
|
||||
if (or (if want-byte-compile (doom--elc-file-outdated-p file))
|
||||
(if want-native-compile (doom--eln-file-outdated-p file)))
|
||||
do (setq outdated t)
|
||||
(when want-native
|
||||
(push file doom--eln-output-expected))
|
||||
finally return outdated))
|
||||
(puthash package t straight--packages-to-rebuild))))
|
||||
(straight-use-package (intern package))))
|
||||
(progn
|
||||
(doom--compile-site-packages)
|
||||
(doom--wait-for-compile-jobs)
|
||||
(doom--write-missing-eln-errors)
|
||||
(print! (success "\033[KRebuilt %d package(s)") (length built)))
|
||||
(print! (success "No packages need rebuilding"))
|
||||
nil))))
|
||||
|
||||
|
||||
|
||||
(defun doom-cli-packages-update ()
|
||||
"Updates packages."
|
||||
(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 ((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)
|
||||
(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)
|
||||
(print! (success "\033[K(%d/%d) %s updated (%s -> %s)")
|
||||
i total local-repo
|
||||
(doom--abbrev-commit ref)
|
||||
(doom--abbrev-commit target-ref))
|
||||
(unless (string-empty-p output)
|
||||
(print-group! (print! "%s" (indent 2 output)))))
|
||||
(user-error
|
||||
(signal 'user-error (error-message-string e)))
|
||||
(error
|
||||
(signal 'doom-package-error (list package e)))))))
|
||||
(print-group!
|
||||
(princ "\033[K")
|
||||
(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))))
|
||||
|
||||
|
||||
;;; PURGE (for the emperor)
|
||||
(defun doom--cli-packages-purge-build (build)
|
||||
(let ((build-dir (straight--build-dir build)))
|
||||
(delete-directory build-dir 'recursive)
|
||||
(if (file-directory-p build-dir)
|
||||
(ignore (print! (error "Failed to purg build/%s" build)))
|
||||
(print! (success "Purged build/%s" build))
|
||||
t)))
|
||||
|
||||
(defun doom--cli-packages-purge-builds (builds)
|
||||
(if (not 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))))))
|
||||
|
||||
(cl-defun doom--cli-packages-regraft-repo (repo)
|
||||
(let ((default-directory (straight--repos-dir repo)))
|
||||
(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)
|
||||
(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))
|
||||
(princ "\033[K")
|
||||
(let ((after-size (doom-directory-size (straight--repos-dir))))
|
||||
(print! (success "Finished regrafting. Size before: %0.1fKB and after: %0.1fKB (%0.1fKB)")
|
||||
before-size after-size
|
||||
(- after-size before-size))))))))
|
||||
|
||||
(defun doom--cli-packages-purge-repo (repo)
|
||||
(let ((repo-dir (straight--repos-dir repo)))
|
||||
(delete-directory repo-dir 'recursive)
|
||||
(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))
|
||||
t)))
|
||||
|
||||
(defun doom--cli-packages-purge-repos (repos)
|
||||
(if (not 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 ()
|
||||
(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.
|
||||
|
||||
An orphaned package is a package that isn't a primary package (i.e. doesn't have
|
||||
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)."
|
||||
(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
|
||||
(and (or repos-p regraft-repos-p)
|
||||
(straight--directory-files (straight--repos-dir) nil nil 'sort))))
|
||||
(list (when builds-p
|
||||
(let ((default-directory (straight--build-dir)))
|
||||
(seq-filter #'file-directory-p
|
||||
(seq-remove (doom-rpartial #'gethash straight--profile-cache)
|
||||
(straight--directory-files default-directory 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)))))))
|
|
@ -0,0 +1,56 @@
|
|||
;;; core/cli/sync.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defcli! (sync s)
|
||||
((no-envvar-p ["-e"] "Don't regenerate the envvar file")
|
||||
(no-elc-p ["-c"] "Don't recompile config")
|
||||
(update-p ["-u"] "Update installed packages after syncing")
|
||||
(purge-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:
|
||||
|
||||
1. Modify your `doom!' block,
|
||||
2. Add or remove `package!' blocks to your config,
|
||||
3. Add or remove autoloaded functions in module autoloaded files.
|
||||
4. Update Doom outside of Doom (e.g. with git)
|
||||
|
||||
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."
|
||||
(run-hooks 'doom-sync-pre-hook)
|
||||
(add-hook 'kill-emacs-hook #'doom--cli-abort-warning-h)
|
||||
(print! (start "Synchronizing your config with Doom Emacs..."))
|
||||
(unwind-protect
|
||||
(print-group!
|
||||
(delete-file doom-autoloads-file)
|
||||
(when (and (not no-envvar-p)
|
||||
(file-exists-p doom-env-file))
|
||||
(doom-cli-reload-env-file 'force))
|
||||
(doom-cli-packages-install)
|
||||
(doom-cli-packages-build)
|
||||
(when update-p
|
||||
(doom-cli-packages-update))
|
||||
(doom-cli-packages-purge purge-p 'builds-p purge-p purge-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)
|
||||
(remove-hook 'kill-emacs-hook #'doom--cli-abort-warning-h)))
|
||||
|
||||
|
||||
;;
|
||||
;;; DEPRECATED Commands
|
||||
|
||||
(defcli! (refresh re) ()
|
||||
"Deprecated for 'doom sync'"
|
||||
:hidden t
|
||||
(user-error "'doom refresh' has been replaced with 'doom sync'. Use that instead"))
|
||||
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(defun doom--cli-abort-warning-h ()
|
||||
(terpri)
|
||||
(print! (warn "Script was abruptly aborted! Run 'doom sync' to repair inconsistencies")))
|
|
@ -0,0 +1,104 @@
|
|||
;;; core/cli/test.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defun doom--emacs-binary ()
|
||||
(let ((emacs-binary-path (doom-path invocation-directory invocation-name))
|
||||
(runemacs-binary-path (if IS-WINDOWS (doom-path invocation-directory "runemacs.exe"))))
|
||||
(if (and runemacs-binary-path (file-exists-p runemacs-binary-path))
|
||||
runemacs-binary-path
|
||||
emacs-binary-path)))
|
||||
|
||||
|
||||
(defcli! test (&rest targets)
|
||||
"Run Doom unit tests."
|
||||
:bare t
|
||||
(doom-initialize 'force 'noerror)
|
||||
(require 'ansi-color)
|
||||
(let (files read-files)
|
||||
(unless targets
|
||||
(setq targets
|
||||
(cons doom-core-dir
|
||||
(cl-remove-if-not
|
||||
(doom-rpartial #'file-in-directory-p doom-emacs-dir)
|
||||
;; Omit `doom-private-dir', which is always first
|
||||
(let (doom-modules)
|
||||
(load (expand-file-name "test/init" doom-core-dir) nil t)
|
||||
(cdr (doom-module-load-path)))))))
|
||||
(while targets
|
||||
(let ((target (pop targets)))
|
||||
;; FIXME Module targets don't work
|
||||
(cond ((equal target ":core")
|
||||
(appendq! files (nreverse (doom-glob doom-core-dir "test/test-*.el"))))
|
||||
((file-directory-p target)
|
||||
(setq target (expand-file-name target))
|
||||
(appendq! files (nreverse (doom-glob target "test/test-*.el"))))
|
||||
((file-exists-p target)
|
||||
(push target files)))))
|
||||
(setenv "DOOMLOCALDIR" (concat doom-local-dir "test/"))
|
||||
(setenv "DOOMDIR" (concat doom-core-dir "test/"))
|
||||
(with-temp-buffer
|
||||
(print! (start "Bootstrapping test environment, if necessary..."))
|
||||
(cl-destructuring-bind (status . output)
|
||||
(doom-exec-process
|
||||
(doom--emacs-binary)
|
||||
"--batch"
|
||||
"--eval"
|
||||
(prin1-to-string
|
||||
`(progn
|
||||
(setq user-emacs-directory ,doom-emacs-dir
|
||||
doom-auto-accept t)
|
||||
(require 'core ,(locate-library "core"))
|
||||
(require 'core-cli)
|
||||
(doom-initialize 'force 'noerror)
|
||||
(doom-initialize-modules)
|
||||
(doom-cli-reload-core-autoloads)
|
||||
(when (doom-cli-packages-install)
|
||||
(doom-cli-reload-package-autoloads)))))
|
||||
(unless (zerop status)
|
||||
(error "Failed to bootstrap unit tests"))))
|
||||
(with-temp-buffer
|
||||
(dolist (file files)
|
||||
(if (doom-file-cookie-p file "if" t)
|
||||
(cl-destructuring-bind (_status . output)
|
||||
(apply #'doom-exec-process
|
||||
(doom--emacs-binary)
|
||||
"--batch"
|
||||
"-l" (concat doom-core-dir "core.el")
|
||||
"-l" (concat doom-core-dir "test/helpers.el")
|
||||
(append (when (file-in-directory-p file doom-modules-dir)
|
||||
(list "-f" "doom-initialize-core"))
|
||||
(list "-l" file
|
||||
"-f" "buttercup-run")))
|
||||
(insert (replace-regexp-in-string ansi-color-control-seq-regexp "" output))
|
||||
(push file read-files))
|
||||
(print! (info "Ignoring %s" (relpath file)))))
|
||||
(let ((total 0)
|
||||
(total-failed 0)
|
||||
(i 0))
|
||||
(print! "\n----------------------------------------\nTests finished")
|
||||
(print-group!
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^Ran \\([0-9]+\\) specs, \\([0-9]+\\) failed," nil t)
|
||||
(let ((ran (string-to-number (match-string 1)))
|
||||
(failed (string-to-number (match-string 2))))
|
||||
(when (> failed 0)
|
||||
(terpri)
|
||||
(print! (warn "(%s) Failed %d/%d tests")
|
||||
(path (nth i read-files))
|
||||
failed ran)
|
||||
(save-excursion
|
||||
(print-group!
|
||||
(print!
|
||||
"%s" (string-trim
|
||||
(buffer-substring
|
||||
(match-beginning 0)
|
||||
(dotimes (_ failed (point))
|
||||
(search-backward "========================================"))))))))
|
||||
(cl-incf total ran)
|
||||
(cl-incf total-failed failed)
|
||||
(cl-incf i))))
|
||||
(terpri)
|
||||
(if (= total-failed 0)
|
||||
(print! (success "Ran %d tests successfully." total total-failed))
|
||||
(print! (error "Ran %d tests, %d failed") total total-failed)
|
||||
(kill-emacs 1)))
|
||||
t)))
|
|
@ -0,0 +1,126 @@
|
|||
;;; core/cli/upgrade.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defcli! (upgrade up)
|
||||
((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
|
||||
following shell commands:
|
||||
|
||||
cd ~/.emacs.d
|
||||
git pull --rebase
|
||||
bin/doom clean
|
||||
bin/doom sync -u"
|
||||
:bare t
|
||||
(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"))
|
||||
(throw 'exit (list "doom" "upgrade" "-p" (if force-p "-f"))))
|
||||
|
||||
((print! "Doom is up-to-date!")
|
||||
(doom-cli-execute "sync" "-u")))))
|
||||
|
||||
|
||||
;;
|
||||
;;; library
|
||||
|
||||
(defvar doom-repo-url "https://github.com/hlissner/doom-emacs"
|
||||
"The git repo url for Doom Emacs.")
|
||||
(defvar doom-repo-remote "_upgrade"
|
||||
"The name to use as our staging remote.")
|
||||
|
||||
(defun doom--working-tree-dirty-p (dir)
|
||||
(cl-destructuring-bind (success . stdout)
|
||||
(doom-call-process "git" "status" "--porcelain" "-uno")
|
||||
(if (= 0 success)
|
||||
(split-string stdout "\n" t)
|
||||
(error "Failed to check working tree in %s" dir))))
|
||||
|
||||
|
||||
(defun doom-cli-upgrade (&optional auto-accept-p force-p)
|
||||
"Upgrade Doom to the latest version non-destructively."
|
||||
(let ((default-directory doom-emacs-dir)
|
||||
process-file-side-effects)
|
||||
(print! (start "Preparing to upgrade Doom Emacs and its packages..."))
|
||||
|
||||
(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)
|
||||
"Couldn't find Doom's .git directory. Was Doom cloned properly?"
|
||||
"Couldn't detect what branch you're on. Is Doom detached?")))
|
||||
|
||||
;; We assume that a dirty .emacs.d is intentional and abort
|
||||
(when-let (dirty (doom--working-tree-dirty-p default-directory))
|
||||
(if (not force-p)
|
||||
(user-error! "%s\n\n%s\n\n %s"
|
||||
(format "Refusing to upgrade because %S has been modified." (path doom-emacs-dir))
|
||||
"Either stash/undo your changes or run 'doom upgrade -f' to discard local changes."
|
||||
(string-join dirty "\n"))
|
||||
(print! (info "You have local modifications in Doom's source. Discarding them..."))
|
||||
(doom-call-process "git" "reset" "--hard" (format "origin/%s" branch))
|
||||
(doom-call-process "git" "clean" "-ffd")))
|
||||
|
||||
(doom-call-process "git" "remote" "remove" doom-repo-remote)
|
||||
(unwind-protect
|
||||
(let (result)
|
||||
(or (zerop (car (doom-call-process "git" "remote" "add" doom-repo-remote doom-repo-url)))
|
||||
(error "Failed to add %s to remotes" doom-repo-remote))
|
||||
(or (zerop (car (setq result (doom-call-process "git" "fetch" "--force" "--tags" doom-repo-remote (format "%s:%s" branch target-remote)))))
|
||||
(error "Failed to fetch from upstream"))
|
||||
|
||||
(let ((this-rev (cdr (doom-call-process "git" "rev-parse" "HEAD")))
|
||||
(new-rev (cdr (doom-call-process "git" "rev-parse" target-remote))))
|
||||
(cond
|
||||
((and (null this-rev)
|
||||
(null new-rev))
|
||||
(error "Failed to get revisions for %s" target-remote))
|
||||
|
||||
((equal this-rev new-rev)
|
||||
(print! (success "Doom is already up-to-date!"))
|
||||
nil)
|
||||
|
||||
((print! (info "A new version of Doom Emacs is available!\n\n Old revision: %s (%s)\n New revision: %s (%s)\n"
|
||||
(substring this-rev 0 10)
|
||||
(cdr (doom-call-process "git" "log" "-1" "--format=%cr" "HEAD"))
|
||||
(substring new-rev 0 10)
|
||||
(cdr (doom-call-process "git" "log" "-1" "--format=%cr" target-remote))))
|
||||
(let ((diff-url
|
||||
(format "%s/compare/%s...%s"
|
||||
doom-repo-url
|
||||
this-rev
|
||||
new-rev)))
|
||||
(print! "Link to diff: %s" diff-url)
|
||||
(when (and (not auto-accept-p)
|
||||
(y-or-n-p "View the comparison diff in your browser?"))
|
||||
(print! (info "Opened github in your browser."))
|
||||
(browse-url diff-url)))
|
||||
|
||||
(if (not (or auto-accept-p
|
||||
(y-or-n-p "Proceed with upgrade?")))
|
||||
(ignore (print! (error "Aborted")))
|
||||
(print! (start "Upgrading Doom Emacs..."))
|
||||
(print-group!
|
||||
(doom-clean-byte-compiled-files)
|
||||
(or (and (zerop (car (doom-call-process "git" "reset" "--hard" target-remote)))
|
||||
(equal (cdr (doom-call-process "git" "rev-parse" "HEAD")) new-rev))
|
||||
(error "Failed to check out %s" (substring new-rev 0 10)))
|
||||
(print! (info "%s") (cdr result))
|
||||
t))))))
|
||||
(ignore-errors
|
||||
(doom-call-process "git" "branch" "-D" target-remote)
|
||||
(doom-call-process "git" "remote" "remove" doom-repo-remote))))))
|
|
@ -0,0 +1,573 @@
|
|||
;;; core/core-cli.el --- -*- lexical-binding: t; no-byte-compile: t; -*-
|
||||
|
||||
(load! "autoload/process")
|
||||
(load! "autoload/plist")
|
||||
(load! "autoload/files")
|
||||
(load! "autoload/output")
|
||||
(require 'seq)
|
||||
|
||||
;; 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)
|
||||
|
||||
;; Don't generate superfluous files when writing temp buffers
|
||||
(setq make-backup-files nil)
|
||||
|
||||
;; Stop user configuration from interfering with package management
|
||||
(setq enable-dir-local-variables nil)
|
||||
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
(defvar doom-auto-accept (getenv "YES")
|
||||
"If non-nil, Doom will auto-accept any confirmation prompts during batch
|
||||
commands like `doom-cli-packages-install', `doom-cli-packages-update' and
|
||||
`doom-packages-autoremove'.")
|
||||
|
||||
(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-log-file (concat doom-local-dir "doom.log")
|
||||
"File to write the extended output to.")
|
||||
|
||||
(defvar doom-cli-log-error-file (concat doom-local-dir "doom.error.log")
|
||||
"File to write the last backtrace to.")
|
||||
|
||||
(defvar doom--cli-commands (make-hash-table :test 'equal))
|
||||
(defvar doom--cli-groups (make-hash-table :test 'equal))
|
||||
(defvar doom--cli-group nil)
|
||||
|
||||
(define-error 'doom-cli-error "There was an unexpected error" 'doom-error)
|
||||
(define-error 'doom-cli-command-not-found-error "Could not find that command" 'doom-cli-error)
|
||||
(define-error 'doom-cli-wrong-number-of-arguments-error "Wrong number of CLI arguments" 'doom-cli-error)
|
||||
(define-error 'doom-cli-unrecognized-option-error "Not a recognized option" 'doom-cli-error)
|
||||
(define-error 'doom-cli-deprecated-error "Command is deprecated" 'doom-cli-error)
|
||||
|
||||
|
||||
;;
|
||||
;;; CLI library
|
||||
|
||||
(cl-defstruct
|
||||
(doom-cli
|
||||
(:constructor nil)
|
||||
(:constructor
|
||||
make-doom-cli
|
||||
(name &key desc aliases optlist arglist plist fn
|
||||
&aux
|
||||
(optlist
|
||||
(cl-loop for (symbol options desc) in optlist
|
||||
for ((_ . options) (_ . params))
|
||||
= (seq-group-by #'stringp options)
|
||||
collect
|
||||
(make-doom-cli-option :symbol symbol
|
||||
:flags options
|
||||
:args params
|
||||
:desc desc))))))
|
||||
(name nil :read-only t)
|
||||
(desc "TODO")
|
||||
aliases
|
||||
optlist
|
||||
arglist
|
||||
plist
|
||||
(fn (lambda (_) (print! "But nobody came!"))))
|
||||
|
||||
(cl-defstruct doom-cli-option
|
||||
(symbol)
|
||||
(flags ())
|
||||
(args ())
|
||||
(desc "TODO"))
|
||||
|
||||
(defun doom--cli-get-option (cli flag)
|
||||
(cl-find-if (doom-partial #'member flag)
|
||||
(doom-cli-optlist cli)
|
||||
:key #'doom-cli-option-flags))
|
||||
|
||||
(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)))
|
||||
(got 0)
|
||||
restvar
|
||||
rest
|
||||
alist)
|
||||
(catch 'done
|
||||
(while args
|
||||
(let ((arg (pop args)))
|
||||
(cond ((eq (car arglist) '&rest)
|
||||
(setq restvar (cadr arglist)
|
||||
rest (cons arg args))
|
||||
(throw 'done t))
|
||||
|
||||
((string-match "^\\(--\\([a-zA-Z0-9][a-zA-Z0-9-_]*\\)\\)\\(?:=\\(.+\\)\\)?$" arg)
|
||||
(let* ((fullflag (match-string 1 arg))
|
||||
(opt (doom--cli-get-option cli fullflag)))
|
||||
(unless opt
|
||||
(user-error "Unrecognized switch %S" (concat "--" (match-string 2 arg))))
|
||||
(setf (alist-get (doom-cli-option-symbol opt) alist)
|
||||
(or (if (doom-cli-option-args opt)
|
||||
(or (match-string 3 arg)
|
||||
(pop args)
|
||||
(user-error "%S expected an argument, but got none"
|
||||
fullflag))
|
||||
(if (match-string 3 arg)
|
||||
(user-error "%S was not expecting an argument, but got %S"
|
||||
fullflag (match-string 3 arg))
|
||||
fullflag))))))
|
||||
|
||||
((string-match "^\\(-\\([a-zA-Z0-9]+\\)\\)$" arg)
|
||||
(let ((fullflag (match-string 1 arg))
|
||||
(flag (match-string 2 arg)))
|
||||
(dolist (switch (split-string flag "" t))
|
||||
(if-let (opt (doom--cli-get-option cli (concat "-" switch)))
|
||||
(setf (alist-get (doom-cli-option-symbol opt) alist)
|
||||
(if (doom-cli-option-args opt)
|
||||
(or (pop args)
|
||||
(user-error "%S expected an argument, but got none"
|
||||
fullflag))
|
||||
fullflag))
|
||||
(user-error "Unrecognized switch %S" (concat "-" switch))))))
|
||||
|
||||
(arglist
|
||||
(cl-incf got)
|
||||
(let ((spec (pop arglist)))
|
||||
(when (eq spec '&optional)
|
||||
(setq spec (pop arglist)))
|
||||
(setf (alist-get spec alist) arg))
|
||||
(when (null arglist)
|
||||
(throw 'done t)))
|
||||
|
||||
(t
|
||||
(push arg args)
|
||||
(throw 'done t))))))
|
||||
(when (< got expected)
|
||||
(error "Expected %d arguments, got %d" expected got))
|
||||
(when rest
|
||||
(setf (alist-get restvar alist) rest))
|
||||
alist))
|
||||
|
||||
(defun doom-cli-get (command)
|
||||
"Return a CLI object associated by COMMAND name (string)."
|
||||
(cond ((null command) nil)
|
||||
((doom-cli-p command) command)
|
||||
((doom-cli-get
|
||||
(gethash (cond ((symbolp command) command)
|
||||
((stringp command) (intern command))
|
||||
(command))
|
||||
doom--cli-commands)))))
|
||||
|
||||
(defun doom-cli-internal-p (cli)
|
||||
"Return non-nil if CLI is an internal (non-public) command."
|
||||
(string-prefix-p ":" (doom-cli-name cli)))
|
||||
|
||||
(defun doom-cli-execute (command &rest args)
|
||||
"Execute COMMAND (string) with ARGS (list of strings).
|
||||
|
||||
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 (remq nil args)))
|
||||
(user-error "Couldn't find any %S command" command)))
|
||||
|
||||
(defmacro defcli! (name speclist &optional docstring &rest body)
|
||||
"Defines a CLI command.
|
||||
|
||||
COMMAND is a symbol or a list of symbols representing the aliases for this
|
||||
command. DOCSTRING is a string description; its first line should be short
|
||||
(under 60 characters), as it will be used as a summary for 'doom help'.
|
||||
|
||||
SPECLIST is a specification for options and arguments, which can be a list
|
||||
specification for an option/switch in the following format:
|
||||
|
||||
(VAR [FLAGS... ARGS...] DESCRIPTION)
|
||||
|
||||
Otherwise, SPECLIST accepts the same argument specifiers as `defun'.
|
||||
|
||||
BODY will be run when this dispatcher is called."
|
||||
(declare (indent 2) (doc-string 3))
|
||||
(unless (stringp docstring)
|
||||
(push docstring body)
|
||||
(setq docstring "TODO"))
|
||||
(let ((names (doom-enlist name))
|
||||
(optlist (cl-remove-if-not #'listp speclist))
|
||||
(arglist (cl-remove-if #'listp speclist))
|
||||
(plist (cl-loop for (key val) on body by #'cddr
|
||||
if (keywordp key)
|
||||
nconc (list key val) into plist
|
||||
else return plist)))
|
||||
`(let ((name ',(car names))
|
||||
(aliases ',(cdr names))
|
||||
(plist ',plist))
|
||||
(when doom--cli-group
|
||||
(setq plist (plist-put plist :group doom--cli-group)))
|
||||
(puthash
|
||||
name
|
||||
(make-doom-cli (symbol-name name)
|
||||
:desc ,docstring
|
||||
:aliases (mapcar #'symbol-name aliases)
|
||||
:arglist ',arglist
|
||||
:optlist ',optlist
|
||||
:plist plist
|
||||
:fn
|
||||
(lambda (--alist--)
|
||||
(ignore --alist--)
|
||||
(let ,(cl-loop for opt in speclist
|
||||
for optsym = (if (listp opt) (car opt) opt)
|
||||
unless (memq optsym cl--lambda-list-keywords)
|
||||
collect (list optsym `(cdr (assq ',optsym --alist--))))
|
||||
,@body)))
|
||||
doom--cli-commands)
|
||||
(when aliases
|
||||
(mapc (doom-rpartial #'puthash name doom--cli-commands)
|
||||
aliases)))))
|
||||
|
||||
(defmacro defcligroup! (name docstring &rest body)
|
||||
"Declare all enclosed cli commands are part of the NAME group."
|
||||
(declare (indent defun) (doc-string 2))
|
||||
`(let ((doom--cli-group ,name))
|
||||
(puthash doom--cli-group ,docstring doom--cli-groups)
|
||||
,@body))
|
||||
|
||||
|
||||
;;
|
||||
;;; Debugger
|
||||
|
||||
(cl-defun doom-cli--debugger (error data)
|
||||
(cl-incf num-nonmacro-input-events)
|
||||
(cl-destructuring-bind (backtrace &optional type data . _)
|
||||
(cons (doom-cli--backtrace) data)
|
||||
(cond
|
||||
((and (bound-and-true-p straight-process-buffer)
|
||||
(stringp data)
|
||||
(string-match-p (regexp-quote straight-process-buffer)
|
||||
data))
|
||||
(print! (error "There was an unexpected package error"))
|
||||
(print-group!
|
||||
(print! "%s" (string-trim-right (straight--process-get-output)))))
|
||||
((print! (error "There was an unexpected error"))
|
||||
(print-group!
|
||||
(print! "%s %s" (bold "Message:") (get type 'error-message))
|
||||
(print! "%s %S" (bold "Data:") (cons type data))
|
||||
(when backtrace
|
||||
(print! (bold "Backtrace:"))
|
||||
(print-group!
|
||||
(dolist (frame (seq-take backtrace 10))
|
||||
(print!
|
||||
"%0.74s" (replace-regexp-in-string
|
||||
"[\n\r]" "\\\\n" (format "%S" frame)))))))))
|
||||
(when backtrace
|
||||
(with-temp-file doom-cli-log-error-file
|
||||
(insert "# -*- lisp-interaction -*-\n")
|
||||
(insert "# vim: set ft=lisp:\n")
|
||||
(let ((standard-output (current-buffer))
|
||||
(print-quoted t)
|
||||
(print-escape-newlines t)
|
||||
(print-escape-control-characters t)
|
||||
(print-level nil)
|
||||
(print-circle nil))
|
||||
(mapc #'print (cons (list type data) backtrace)))
|
||||
(print! (warn "Extended backtrace logged to %s")
|
||||
(relpath doom-cli-log-error-file)))))
|
||||
(throw 'exit 255))
|
||||
|
||||
(defun doom-cli--backtrace ()
|
||||
(let* ((n 0)
|
||||
(frame (backtrace-frame n))
|
||||
(frame-list nil)
|
||||
(in-program-stack nil))
|
||||
(while frame
|
||||
(when in-program-stack
|
||||
(push (cdr frame) frame-list))
|
||||
(when (eq (elt frame 1) 'doom-cli--debugger)
|
||||
(setq in-program-stack t))
|
||||
(when (and (eq (elt frame 1) 'doom-cli-execute)
|
||||
(eq (elt frame 2) :doom))
|
||||
(setq in-program-stack nil))
|
||||
(setq n (1+ n)
|
||||
frame (backtrace-frame n)))
|
||||
(reverse frame-list)))
|
||||
|
||||
|
||||
;;
|
||||
;;; 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)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Entry point
|
||||
|
||||
(defcli! :doom
|
||||
((help-p ["-h" "--help"] "Same as help command")
|
||||
(auto-accept-p ["-y" "--yes"] "Auto-accept all confirmation prompts")
|
||||
(debug-p ["-d" "--debug"] "Enables on verbose output")
|
||||
(doomdir ["--doomdir" dir] "Use the private module at DIR (e.g. ~/.doom.d)")
|
||||
(localdir ["--localdir" dir] "Use DIR as your local storage directory")
|
||||
&optional command
|
||||
&rest args)
|
||||
"A command line interface for managing Doom Emacs.
|
||||
|
||||
Includes package management, diagnostics, unit tests, and byte-compilation.
|
||||
|
||||
This tool also makes it trivial to launch Emacs out of a different folder or
|
||||
with a different private module.
|
||||
|
||||
Environment variables:
|
||||
EMACSDIR Where to find the Doom Emacs repo (normally ~/.emacs.d)
|
||||
DOOMDIR Where to find your private Doom config (normally ~/.doom.d)
|
||||
DOOMLOCALDIR Where to store local files (normally ~/.emacs.d/.local)"
|
||||
(condition-case e
|
||||
(with-output-to! doom-cli-log-file
|
||||
(catch 'exit
|
||||
(when (and (not (getenv "__DOOMRESTART"))
|
||||
(or doomdir
|
||||
localdir
|
||||
debug-p
|
||||
auto-accept-p))
|
||||
(when doomdir
|
||||
(setenv "DOOMDIR" (file-name-as-directory doomdir))
|
||||
(print! (info "DOOMDIR=%s") localdir))
|
||||
(when localdir
|
||||
(setenv "DOOMLOCALDIR" (file-name-as-directory localdir))
|
||||
(print! (info "DOOMLOCALDIR=%s") localdir))
|
||||
(when debug-p
|
||||
(setenv "DEBUG" "1")
|
||||
(print! (info "DEBUG=1")))
|
||||
(when auto-accept-p
|
||||
(setenv "YES" auto-accept-p)
|
||||
(print! (info "Confirmations auto-accept enabled")))
|
||||
(throw 'exit "__DOOMRESTART=1 $@"))
|
||||
;; TODO Rotate logs out, instead of overwriting them?
|
||||
(delete-file doom-cli-log-file)
|
||||
(delete-file doom-cli-log-error-file)
|
||||
(when help-p
|
||||
(when command
|
||||
(push command args))
|
||||
(setq command "help"))
|
||||
(if (null command)
|
||||
(doom-cli-execute "help")
|
||||
(let ((start-time (current-time)))
|
||||
(run-hooks 'doom-cli-pre-hook)
|
||||
(when (apply #'doom-cli-execute command args)
|
||||
(run-hooks 'doom-cli-post-hook)
|
||||
(print! (success "Finished in %.4fs")
|
||||
(float-time (time-subtract (current-time) start-time))))))))
|
||||
;; TODO Not implemented yet
|
||||
(doom-cli-command-not-found-error
|
||||
(print! (error "Command 'doom %s' not recognized") (string-join (cdr e) " "))
|
||||
(print! "\nDid you mean one of these commands?")
|
||||
(apply #'doom-cli-execute "help" "--similar" (string-join (cdr e) " "))
|
||||
2)
|
||||
;; TODO Not implemented yet
|
||||
(doom-cli-wrong-number-of-arguments-error
|
||||
(cl-destructuring-bind (route opt arg n d) (cdr e)
|
||||
(print! (error "doom %s: %S requires %d arguments, but %d given\n")
|
||||
(mapconcat #'symbol-name route " ") arg n d)
|
||||
(print-group!
|
||||
(apply #'doom-cli-execute "help" (mapcar #'symbol-name route))))
|
||||
3)
|
||||
;; TODO Not implemented yet
|
||||
(doom-cli-unrecognized-option-error
|
||||
(let ((option (cadr e)))
|
||||
(print! (error "Unrecognized option: %S") option)
|
||||
(when (string-match "^--[^=]+=\\(.+\\)$" option)
|
||||
(print! "The %S syntax isn't supported. Use '%s %s' instead."
|
||||
option (car (split-string option "="))
|
||||
(match-string 1 option))))
|
||||
4)
|
||||
;; TODO Not implemented yet
|
||||
(doom-cli-deprecated-error
|
||||
(cl-destructuring-bind (route . commands) (cdr e)
|
||||
(print! (warn "The 'doom %s' command was removed and replaced with:\n")
|
||||
(mapconcat #'symbol-name route " "))
|
||||
(print-group!
|
||||
(dolist (command commands)
|
||||
(print! (info "%s") command))))
|
||||
5)
|
||||
(user-error
|
||||
(print! (warn "%s") (cadr e))
|
||||
1)))
|
||||
|
||||
|
||||
;;
|
||||
;;; CLI Commands
|
||||
|
||||
(load! "cli/help")
|
||||
(load! "cli/install")
|
||||
(load! "cli/sync")
|
||||
(load! "cli/env")
|
||||
(load! "cli/upgrade")
|
||||
(load! "cli/packages")
|
||||
(load! "cli/autoloads")
|
||||
|
||||
(defcligroup! "Diagnostics"
|
||||
"For troubleshooting and diagnostics"
|
||||
(load! "cli/doctor")
|
||||
(load! "cli/debug")
|
||||
|
||||
;; 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 (&rest args)
|
||||
"Run Doom Emacs from bin/doom's parent directory.
|
||||
|
||||
All arguments are passed on to Emacs.
|
||||
|
||||
doom run
|
||||
doom run -nw init.el
|
||||
|
||||
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."
|
||||
(throw 'exit (cons invocation-name args))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Bootstrap
|
||||
|
||||
(doom-log "Initializing Doom CLI")
|
||||
(load! doom-module-init-file doom-private-dir t)
|
||||
(maphash (doom-module-loader doom-cli-file) doom-modules)
|
||||
(load! doom-cli-file doom-private-dir t)
|
||||
|
||||
(provide 'core-cli)
|
||||
;;; core-cli.el ends here
|
|
@ -0,0 +1,602 @@
|
|||
;;; core-editor.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-detect-indentation-excluded-modes '(fundamental-mode so-long-mode)
|
||||
"A list of major modes in which indentation should be automatically
|
||||
detected.")
|
||||
|
||||
(defvar-local doom-inhibit-indent-detection nil
|
||||
"A buffer-local flag that indicates whether `dtrt-indent' should try to detect
|
||||
indentation settings or not. This should be set by editorconfig if it
|
||||
successfully sets indent_style/indent_size.")
|
||||
|
||||
(defvar doom-inhibit-large-file-detection nil
|
||||
"If non-nil, inhibit large/long file detection when opening files.")
|
||||
|
||||
(defvar doom-large-file-p nil)
|
||||
(put 'doom-large-file-p 'permanent-local t)
|
||||
|
||||
(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
|
||||
becoming unusably slow.
|
||||
|
||||
These thresholds are in MB, and is used by `doom--optimize-for-large-files-a'.")
|
||||
|
||||
(defvar doom-large-file-excluded-modes
|
||||
'(so-long-mode special-mode archive-mode tar-mode jka-compr
|
||||
git-commit-mode image-mode doc-view-mode doc-view-mode-maybe
|
||||
ebrowse-tree-mode pdf-view-mode tags-table-mode)
|
||||
"Major modes that `doom-check-large-file-h' will ignore.")
|
||||
|
||||
|
||||
;;
|
||||
;;; File handling
|
||||
|
||||
(defadvice! doom--prepare-for-large-files-a (size _ filename &rest _)
|
||||
"Sets `doom-large-file-p' if the file is considered large.
|
||||
|
||||
Uses `doom-large-file-size-alist' to determine when a file is too large. When
|
||||
`doom-large-file-p' is set, other plugins can detect this and reduce their
|
||||
runtime costs (or disable themselves) to ensure the buffer is as fast as
|
||||
possible."
|
||||
:before #'abort-if-file-too-large
|
||||
(and (numberp size)
|
||||
(null doom-inhibit-large-file-detection)
|
||||
(ignore-errors
|
||||
(> size
|
||||
(* 1024 1024
|
||||
(assoc-default filename doom-large-file-size-alist
|
||||
#'string-match-p))))
|
||||
(setq-local doom-large-file-p size)))
|
||||
|
||||
(add-hook! 'find-file-hook
|
||||
(defun doom-optimize-for-large-files-h ()
|
||||
"Trigger `so-long-minor-mode' if the file is large."
|
||||
(when (and doom-large-file-p buffer-file-name)
|
||||
(if (or doom-inhibit-large-file-detection
|
||||
(memq major-mode doom-large-file-excluded-modes))
|
||||
(kill-local-variable 'doom-large-file-p)
|
||||
(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...")))))
|
||||
|
||||
|
||||
;; Resolve symlinks when opening files, so that any operations are conducted
|
||||
;; from the file's true directory (like `find-file').
|
||||
(setq find-file-visit-truename t
|
||||
vc-follow-symlinks t)
|
||||
|
||||
;; Disable the warning "X and Y are the same file". It's fine to ignore this
|
||||
;; warning as it will redirect you to the existing buffer anyway.
|
||||
(setq find-file-suppress-same-file-warnings t)
|
||||
|
||||
;; Create missing directories when we open a file that doesn't exist under a
|
||||
;; directory tree that may not exist.
|
||||
(add-hook! 'find-file-not-found-functions
|
||||
(defun doom-create-missing-directories-h ()
|
||||
"Automatically create missing directories when creating new files."
|
||||
(unless (file-remote-p buffer-file-name)
|
||||
(let ((parent-directory (file-name-directory buffer-file-name)))
|
||||
(and (not (file-directory-p parent-directory))
|
||||
(y-or-n-p (format "Directory `%s' does not exist! Create it?"
|
||||
parent-directory))
|
||||
(progn (make-directory parent-directory 'parents)
|
||||
t))))))
|
||||
|
||||
;; Don't generate backups or lockfiles. While auto-save maintains a copy so long
|
||||
;; as a buffer is unsaved, backups create copies once, when the file is first
|
||||
;; written, and never again until it is killed and reopened. This is better
|
||||
;; suited to version control, and I don't want world-readable copies of
|
||||
;; potentially sensitive material floating around our filesystem.
|
||||
(setq create-lockfiles nil
|
||||
make-backup-files nil
|
||||
;; But in case the user does enable it, some sensible defaults:
|
||||
version-control t ; number each backup file
|
||||
backup-by-copying t ; instead of renaming current file (clobbers links)
|
||||
delete-old-versions t ; clean up after itself
|
||||
kept-old-versions 5
|
||||
kept-new-versions 5
|
||||
backup-directory-alist (list (cons "." (concat doom-cache-dir "backup/")))
|
||||
tramp-backup-directory-alist backup-directory-alist)
|
||||
|
||||
;; But turn on auto-save, so we have a fallback in case of crashes or lost data.
|
||||
;; Use `recover-file' or `recover-session' to recover them.
|
||||
(setq auto-save-default t
|
||||
;; Don't auto-disable auto-save after deleting big chunks. This defeats
|
||||
;; the purpose of a failsafe. This adds the risk of losing the data we
|
||||
;; just deleted, but I believe that's VCS's jurisdiction, not ours.
|
||||
auto-save-include-big-deletions t
|
||||
;; Keep it out of `doom-emacs-dir' or the local directory.
|
||||
auto-save-list-file-prefix (concat doom-cache-dir "autosave/")
|
||||
tramp-auto-save-directory (concat doom-cache-dir "tramp-autosave/")
|
||||
auto-save-file-name-transforms
|
||||
(list (list "\\`/[^/]*:\\([^/]*/\\)*\\([^/]*\\)\\'"
|
||||
;; Prefix tramp autosaves to prevent conflicts with local ones
|
||||
(concat auto-save-list-file-prefix "tramp-\\2") t)
|
||||
(list ".*" auto-save-list-file-prefix t)))
|
||||
|
||||
(add-hook! 'after-save-hook
|
||||
(defun doom-guess-mode-h ()
|
||||
"Guess major mode when saving a file in `fundamental-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
|
||||
|
||||
;; 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)
|
||||
|
||||
;; Only indent the line when at BOL or in a line's indentation. Anywhere else,
|
||||
;; insert literal indentation.
|
||||
(setq-default tab-always-indent nil)
|
||||
|
||||
;; Make `tabify' and `untabify' only affect indentation. Not tabs/spaces in the
|
||||
;; middle of a line.
|
||||
(setq tabify-regexp "^\t* [ \t]+")
|
||||
|
||||
;; 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)
|
||||
|
||||
;; 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
|
||||
|
||||
;; 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))
|
||||
|
||||
|
||||
;;
|
||||
;;; Extra file extensions to support
|
||||
|
||||
(nconc
|
||||
auto-mode-alist
|
||||
'(("/LICENSE\\'" . text-mode)
|
||||
("\\.log\\'" . text-mode)
|
||||
("rc\\'" . conf-mode)
|
||||
("\\.\\(?:hex\\|nes\\)\\'" . hexl-mode)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Built-in plugins
|
||||
|
||||
(use-package! autorevert
|
||||
;; revert buffers when their files/state have changed
|
||||
:hook (focus-in . doom-auto-revert-buffers-h)
|
||||
:hook (after-save . doom-auto-revert-buffers-h)
|
||||
:hook (doom-switch-buffer . doom-auto-revert-buffer-h)
|
||||
:hook (doom-switch-window . doom-auto-revert-buffer-h)
|
||||
:config
|
||||
(setq auto-revert-verbose t ; let us know when it happens
|
||||
auto-revert-use-notify nil
|
||||
auto-revert-stop-on-user-input nil
|
||||
;; Only prompts for confirmation when buffer is unsaved.
|
||||
revert-without-query (list "."))
|
||||
|
||||
;; `auto-revert-mode' and `global-auto-revert-mode' would, normally, abuse the
|
||||
;; heck out of inotify handles _or_ aggresively poll your buffer list every X
|
||||
;; seconds. Too many inotify handles can grind Emacs to a halt if you preform
|
||||
;; expensive or batch processes on files outside of Emacs (e.g. their mtime
|
||||
;; changes), and polling your buffer list is terribly inefficient as your
|
||||
;; buffer list grows into the tens or hundreds.
|
||||
;;
|
||||
;; So Doom uses a different strategy: we lazily auto revert buffers when the
|
||||
;; user a) saves a file, b) switches to a buffer (or its window), or c) you
|
||||
;; focus Emacs (after using another program). This way, Emacs only ever has to
|
||||
;; operate on, at minimum, a single buffer and, at maximum, X buffers, where X
|
||||
;; is the number of open windows (which is rarely, if ever, over 10).
|
||||
(defun doom-auto-revert-buffer-h ()
|
||||
"Auto revert current buffer, if necessary."
|
||||
(unless (or auto-revert-mode (active-minibuffer-window))
|
||||
(let ((auto-revert-mode t))
|
||||
(auto-revert-handler))))
|
||||
|
||||
(defun doom-auto-revert-buffers-h ()
|
||||
"Auto revert stale buffers in visible windows, if necessary."
|
||||
(dolist (buf (doom-visible-buffers))
|
||||
(with-current-buffer buf
|
||||
(doom-auto-revert-buffer-h)))))
|
||||
|
||||
|
||||
(use-package! recentf
|
||||
;; Keep track of recently opened files
|
||||
:defer-incrementally easymenu tree-widget timer
|
||||
:hook (doom-first-file . recentf-mode)
|
||||
:commands recentf-open-files
|
||||
:config
|
||||
(defun doom--recent-file-truename (file)
|
||||
(if (or (file-remote-p file nil t)
|
||||
(not (file-remote-p file)))
|
||||
(file-truename file)
|
||||
file))
|
||||
(setq recentf-filename-handlers
|
||||
'(;; Text properties inflate the size of recentf's files, and there is
|
||||
;; no purpose in persisting them, so we strip them out.
|
||||
substring-no-properties
|
||||
;; Resolve symlinks of local files. Otherwise we get duplicate
|
||||
;; entries opening symlinks.
|
||||
doom--recent-file-truename
|
||||
;; Replace $HOME with ~, which is more portable, and reduces how much
|
||||
;; horizontal space the recentf listing uses to list recent files.
|
||||
abbreviate-file-name)
|
||||
recentf-save-file (concat doom-cache-dir "recentf")
|
||||
recentf-auto-cleanup 'never
|
||||
recentf-max-menu-items 0
|
||||
recentf-max-saved-items 200)
|
||||
|
||||
(add-hook! '(doom-switch-window-hook write-file-functions)
|
||||
(defun doom--recentf-touch-buffer-h ()
|
||||
"Bump file in recent file list when it is switched or written to."
|
||||
(when buffer-file-name
|
||||
(recentf-add-file buffer-file-name))
|
||||
;; Return nil for `write-file-functions'
|
||||
nil))
|
||||
|
||||
(add-hook! 'dired-mode-hook
|
||||
(defun doom--recentf-add-dired-directory-h ()
|
||||
"Add dired directory to recentf file list."
|
||||
(recentf-add-file default-directory)))
|
||||
|
||||
(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
|
||||
:hook (doom-first-input . savehist-mode)
|
||||
:init
|
||||
(setq savehist-file (concat doom-cache-dir "savehist"))
|
||||
:config
|
||||
(setq savehist-save-minibuffer-history t
|
||||
savehist-autosave-interval nil ; save on kill only
|
||||
savehist-additional-variables
|
||||
'(kill-ring ; persist clipboard
|
||||
mark-ring global-mark-ring ; persist marks
|
||||
search-ring regexp-search-ring)) ; persist searches
|
||||
(add-hook! 'savehist-save-hook
|
||||
(defun doom-unpropertize-kill-ring-h ()
|
||||
"Remove text properties from `kill-ring' for a smaller savehist file."
|
||||
(setq kill-ring (cl-loop for item in kill-ring
|
||||
if (stringp item)
|
||||
collect (substring-no-properties item)
|
||||
else if item collect it)))))
|
||||
|
||||
|
||||
(use-package! saveplace
|
||||
;; persistent point location in buffers
|
||||
: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
|
||||
(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))
|
||||
:config
|
||||
(setq server-auth-dir (concat doom-emacs-dir "server/"))
|
||||
(unless (server-running-p)
|
||||
(server-start)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Packages
|
||||
|
||||
(use-package! better-jumper
|
||||
:hook (doom-first-input . better-jumper-mode)
|
||||
:commands doom-set-jump-a doom-set-jump-maybe-a doom-set-jump-h
|
||||
:preface
|
||||
;; REVIEW Suppress byte-compiler warning spawning a *Compile-Log* buffer at
|
||||
;; startup. This can be removed once gilbertw1/better-jumper#2 is merged.
|
||||
(defvar better-jumper-local-mode nil)
|
||||
:init
|
||||
(global-set-key [remap evil-jump-forward] #'better-jumper-jump-forward)
|
||||
(global-set-key [remap evil-jump-backward] #'better-jumper-jump-backward)
|
||||
(global-set-key [remap xref-pop-marker-stack] #'better-jumper-jump-backward)
|
||||
:config
|
||||
(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)))
|
||||
|
||||
(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
|
||||
(let* ((evil--jumps-jumping t)
|
||||
(better-jumper--jumping t))
|
||||
(apply orig-fn args))))
|
||||
(unless result
|
||||
(with-current-buffer (marker-buffer origin)
|
||||
(better-jumper-set-jump
|
||||
(if (markerp (car args))
|
||||
(car args)
|
||||
origin))))
|
||||
result))
|
||||
|
||||
(defun doom-set-jump-h ()
|
||||
"Run `better-jumper-set-jump' but return nil, for short-circuiting hooks."
|
||||
(better-jumper-set-jump)
|
||||
nil)
|
||||
|
||||
;; Creates a jump point before killing a buffer. This allows you to undo
|
||||
;; killing a buffer easily (only works with file buffers though; it's not
|
||||
;; possible to resurrect special buffers).
|
||||
(advice-add #'kill-current-buffer :around #'doom-set-jump-a)
|
||||
|
||||
;; Create a jump point before jumping with imenu.
|
||||
(advice-add #'imenu :around #'doom-set-jump-a))
|
||||
|
||||
|
||||
(use-package! dtrt-indent
|
||||
;; Automatic detection of indent settings
|
||||
: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)
|
||||
;; Reduced from the default of 5000 for slightly faster analysis
|
||||
(setq dtrt-indent-max-lines 2000)
|
||||
|
||||
;; always keep tab-width up-to-date
|
||||
(push '(t tab-width) dtrt-indent-hook-generic-mapping-list)
|
||||
|
||||
(defvar dtrt-indent-run-after-smie)
|
||||
(defadvice! doom--fix-broken-smie-modes-a (orig-fn arg)
|
||||
"Some smie modes throw errors when trying to guess their indentation, like
|
||||
`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))
|
||||
(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)))))
|
||||
|
||||
|
||||
(use-package! helpful
|
||||
;; a better *help* buffer
|
||||
:commands helpful--read-symbol
|
||||
:init
|
||||
;; Make `apropos' et co search more extensively. They're more useful this way.
|
||||
(setq apropos-do-all t)
|
||||
|
||||
(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."
|
||||
(letf! ((#'describe-function #'helpful-function)
|
||||
(#'describe-variable #'helpful-variable))
|
||||
(apply orig-fn args)))
|
||||
|
||||
(after! apropos
|
||||
;; patch apropos buttons to call helpful instead of help
|
||||
(dolist (fun-bt '(apropos-function apropos-macro apropos-command))
|
||||
(button-type-put
|
||||
fun-bt 'action
|
||||
(lambda (button)
|
||||
(helpful-callable (button-get button 'apropos-symbol)))))
|
||||
(dolist (var-bt '(apropos-variable apropos-user-option))
|
||||
(button-type-put
|
||||
var-bt 'action
|
||||
(lambda (button)
|
||||
(helpful-variable (button-get button 'apropos-symbol)))))))
|
||||
|
||||
|
||||
;;;###package imenu
|
||||
(add-hook 'imenu-after-jump-hook #'recenter)
|
||||
|
||||
|
||||
(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.
|
||||
: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 (and is faster), so...
|
||||
(setq sp-highlight-pair-overlay nil
|
||||
sp-highlight-wrap-overlay nil
|
||||
sp-highlight-wrap-tag-overlay 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)
|
||||
;; Smartparens conditional binds a key to C-g when sp overlays are active
|
||||
;; (even if they're invisible). This disruptively changes the behavior of
|
||||
;; C-g in insert mode, requiring two presses of the key to exit insert mode.
|
||||
;; I don't see the point of this keybind, so...
|
||||
(setq sp-pair-overlay-keymap (make-sparse-keymap)))
|
||||
|
||||
;; The default is 100, because smartparen's scans are relatively expensive
|
||||
;; (especially with large pair lists for some modes), we reduce it, as a
|
||||
;; better compromise between performance and accuracy.
|
||||
(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)
|
||||
|
||||
;; Silence some harmless but annoying echo-area spam
|
||||
(dolist (key '(:unmatched-expression :no-matching-tag))
|
||||
(setf (alist-get key sp-message-alist) nil))
|
||||
|
||||
(add-hook! 'eval-expression-minibuffer-setup-hook
|
||||
(defun doom-init-smartparens-in-eval-expression-h ()
|
||||
"Enable `smartparens-mode' in the minibuffer for `eval-expression'.
|
||||
This includes everything that calls `read--expression', e.g.
|
||||
`edebug-eval-expression' Only enable it if
|
||||
`smartparens-global-mode' is on."
|
||||
(when smartparens-global-mode (smartparens-mode +1))))
|
||||
(add-hook! 'minibuffer-setup-hook
|
||||
(defun doom-init-smartparens-in-minibuffer-maybe-h ()
|
||||
"Enable `smartparens' for non-`eval-expression' commands.
|
||||
Only enable `smartparens-mode' if `smartparens-global-mode' is
|
||||
on."
|
||||
(when (and smartparens-global-mode (memq this-command '(evil-ex)))
|
||||
(smartparens-mode +1))))
|
||||
|
||||
;; You're likely writing lisp in the minibuffer, therefore, disable these
|
||||
;; quote pairs, which lisps doesn't use for strings:
|
||||
(sp-local-pair 'minibuffer-inactive-mode "'" nil :actions nil)
|
||||
(sp-local-pair 'minibuffer-inactive-mode "`" nil :actions nil)
|
||||
|
||||
;; Smartparens breaks evil-mode's replace state
|
||||
(defvar doom-buffer-smartparens-mode nil)
|
||||
(add-hook! 'evil-replace-state-exit-hook
|
||||
(defun doom-enable-smartparens-mode-maybe-h ()
|
||||
(when doom-buffer-smartparens-mode
|
||||
(turn-on-smartparens-mode)
|
||||
(kill-local-variable 'doom-buffer-smartparens-mode))))
|
||||
(add-hook! 'evil-replace-state-entry-hook
|
||||
(defun doom-disable-smartparens-mode-maybe-h ()
|
||||
(when smartparens-mode
|
||||
(setq-local doom-buffer-smartparens-mode t)
|
||||
(turn-off-smartparens-mode)))))
|
||||
|
||||
|
||||
(use-package! so-long
|
||||
:hook (doom-first-file . global-so-long-mode)
|
||||
:config
|
||||
(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',
|
||||
;; however, because long files have a far bigger impact on Emacs performance.
|
||||
(delq! 'font-lock-mode so-long-minor-modes)
|
||||
(delq! 'display-line-numbers-mode so-long-minor-modes)
|
||||
(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))
|
||||
;; But disable everything else that may be unnecessary/expensive for large or
|
||||
;; wide buffers.
|
||||
(appendq! so-long-minor-modes
|
||||
'(flycheck-mode
|
||||
flyspell-mode
|
||||
spell-fu-mode
|
||||
eldoc-mode
|
||||
smartparens-mode
|
||||
highlight-numbers-mode
|
||||
better-jumper-local-mode
|
||||
ws-butler-mode
|
||||
auto-composition-mode
|
||||
undo-tree-mode
|
||||
highlight-indent-guides-mode
|
||||
hl-fill-column-mode))
|
||||
(defun doom-buffer-has-long-lines-p ()
|
||||
(unless (bound-and-true-p visual-line-mode)
|
||||
(let ((so-long-skip-leading-comments
|
||||
;; HACK Fix #2183: `so-long-detected-long-line-p' tries to parse
|
||||
;; comment syntax, but comment state may not be initialized,
|
||||
;; leading to a wrong-type-argument: stringp error.
|
||||
(bound-and-true-p comment-use-syntax)))
|
||||
(so-long-detected-long-line-p))))
|
||||
(setq so-long-predicate #'doom-buffer-has-long-lines-p))
|
||||
|
||||
|
||||
(use-package! ws-butler
|
||||
;; a less intrusive `delete-trailing-whitespaces' on save
|
||||
:hook (doom-first-buffer . ws-butler-global-mode)
|
||||
:config
|
||||
;; ws-butler normally preserves whitespace in the buffer (but strips it from
|
||||
;; the written file). While sometimes convenient, this behavior is not
|
||||
;; intuitive. To the average user it looks like whitespace cleanup is failing,
|
||||
;; which causes folks to redundantly install their own.
|
||||
(setq ws-butler-keep-whitespace-before-point nil))
|
||||
|
||||
(provide 'core-editor)
|
||||
;;; core-editor.el ends here
|
|
@ -0,0 +1,455 @@
|
|||
;;; core-keybinds.el -*- lexical-binding: t; -*-
|
||||
|
||||
;; A centralized keybinds system, integrated with `which-key' to preview
|
||||
;; available keybindings. All built into one powerful macro: `map!'. If evil is
|
||||
;; never loaded, then evil bindings set with `map!' are ignored (i.e. omitted
|
||||
;; entirely for performance reasons).
|
||||
|
||||
(defvar doom-leader-key "SPC"
|
||||
"The leader prefix key for Evil users.")
|
||||
|
||||
(defvar doom-leader-alt-key "M-SPC"
|
||||
"An alternative leader prefix key, used for Insert and Emacs states, and for
|
||||
non-evil users.")
|
||||
|
||||
(defvar doom-localleader-key "SPC m"
|
||||
"The localleader prefix key, for major-mode specific commands.")
|
||||
|
||||
(defvar doom-localleader-alt-key "M-SPC m"
|
||||
"The localleader prefix key, for major-mode specific commands. Used for Insert
|
||||
and Emacs states, and for non-evil users.")
|
||||
|
||||
(defvar doom-leader-map (make-sparse-keymap)
|
||||
"An overriding keymap for <leader> keys.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Keybind settings
|
||||
|
||||
(cond
|
||||
(IS-MAC
|
||||
(setq mac-command-modifier 'super
|
||||
ns-command-modifier 'super
|
||||
mac-option-modifier 'meta
|
||||
ns-option-modifier 'meta
|
||||
;; Free up the right option for character composition
|
||||
mac-right-option-modifier 'none
|
||||
ns-right-option-modifier 'none))
|
||||
(IS-WINDOWS
|
||||
(setq w32-lwindow-modifier 'super
|
||||
w32-rwindow-modifier 'super)))
|
||||
|
||||
;; HACK Fixes Emacs' disturbing inability to distinguish C-i from TAB.
|
||||
(define-key key-translation-map [?\C-i]
|
||||
(cmd! (if (let ((keys (this-single-command-raw-keys)))
|
||||
(and keys
|
||||
(not (cl-position 'tab keys))
|
||||
(not (cl-position 'kp-tab keys))
|
||||
(display-graphic-p)
|
||||
;; Fall back if no <C-i> keybind can be found, otherwise
|
||||
;; we've broken all pre-existing C-i keybinds.
|
||||
(let ((key
|
||||
(doom-lookup-key
|
||||
(vconcat (cl-subseq keys 0 -1) [C-i]))))
|
||||
(not (or (numberp key) (null key))))))
|
||||
[C-i] [?\C-i])))
|
||||
|
||||
|
||||
;;
|
||||
;;; Universal, non-nuclear escape
|
||||
|
||||
;; `keyboard-quit' is too much of a nuclear option. I wanted an ESC/C-g to
|
||||
;; do-what-I-mean. It serves four purposes (in order):
|
||||
;;
|
||||
;; 1. Quit active states; e.g. highlights, searches, snippets, iedit,
|
||||
;; multiple-cursors, recording macros, etc.
|
||||
;; 2. Close popup windows remotely (if it is allowed to)
|
||||
;; 3. Refresh buffer indicators, like git-gutter and flycheck
|
||||
;; 4. Or fall back to `keyboard-quit'
|
||||
;;
|
||||
;; And it should do these things incrementally, rather than all at once. And it
|
||||
;; shouldn't interfere with recording macros or the minibuffer. This may require
|
||||
;; you press ESC/C-g two or three times on some occasions to reach
|
||||
;; `keyboard-quit', but this is much more intuitive.
|
||||
|
||||
(defvar doom-escape-hook nil
|
||||
"A hook run when C-g is pressed (or ESC in normal mode, for evil users).
|
||||
|
||||
More specifically, when `doom/escape' is pressed. If any hook returns non-nil,
|
||||
all hooks after it are ignored.")
|
||||
|
||||
(defun doom/escape (&optional interactive)
|
||||
"Run `doom-escape-hook'."
|
||||
(interactive (list 'interactive))
|
||||
(cond ((minibuffer-window-active-p (minibuffer-window))
|
||||
;; quit the minibuffer if open.
|
||||
(when interactive
|
||||
(setq this-command 'abort-recursive-edit))
|
||||
(abort-recursive-edit))
|
||||
;; Run all escape hooks. If any returns non-nil, then stop there.
|
||||
((run-hook-with-args-until-success 'doom-escape-hook))
|
||||
;; don't abort macros
|
||||
((or defining-kbd-macro executing-kbd-macro) nil)
|
||||
;; Back to the default
|
||||
((unwind-protect (keyboard-quit)
|
||||
(when interactive
|
||||
(setq this-command 'keyboard-quit))))))
|
||||
|
||||
(global-set-key [remap keyboard-quit] #'doom/escape)
|
||||
|
||||
(with-eval-after-load 'eldoc
|
||||
(eldoc-add-command 'doom/escape))
|
||||
|
||||
|
||||
;;
|
||||
;;; General + leader/localleader keys
|
||||
|
||||
(use-package general
|
||||
:init
|
||||
;; Convenience aliases
|
||||
(defalias 'define-key! #'general-def)
|
||||
(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.
|
||||
(defmacro doom--define-leader-key (&rest keys)
|
||||
(let (prefix forms wkforms)
|
||||
(while keys
|
||||
(let ((key (pop keys))
|
||||
(def (pop keys)))
|
||||
(if (keywordp key)
|
||||
(when (memq key '(:prefix :infix))
|
||||
(setq prefix def))
|
||||
(when prefix
|
||||
(setq key `(general--concat t ,prefix ,key)))
|
||||
(let* ((udef (cdr-safe (doom-unquote def)))
|
||||
(bdef (if (general--extended-def-p udef)
|
||||
(general--extract-def (general--normalize-extended-def udef))
|
||||
def)))
|
||||
(unless (eq bdef :ignore)
|
||||
(push `(define-key doom-leader-map (general--kbd ,key)
|
||||
,bdef)
|
||||
forms))
|
||||
(when-let (desc (cadr (memq :which-key udef)))
|
||||
(prependq!
|
||||
wkforms `((which-key-add-key-based-replacements
|
||||
(general--concat t doom-leader-alt-key ,key)
|
||||
,desc)
|
||||
(which-key-add-key-based-replacements
|
||||
(general--concat t doom-leader-key ,key)
|
||||
,desc))))))))
|
||||
(macroexp-progn
|
||||
(append (and wkforms `((after! which-key ,@(nreverse wkforms))))
|
||||
(nreverse forms)))))
|
||||
|
||||
(defmacro define-leader-key! (&rest args)
|
||||
"Define <leader> keys.
|
||||
|
||||
Uses `general-define-key' under the hood, but does not support :states,
|
||||
:wk-full-keys or :keymaps. Use `map!' for a more convenient interface.
|
||||
|
||||
See `doom-leader-key' and `doom-leader-alt-key' to change the leader prefix."
|
||||
`(general-define-key
|
||||
:states nil
|
||||
:wk-full-keys nil
|
||||
:keymaps 'doom-leader-map
|
||||
,@args))
|
||||
|
||||
(defmacro define-localleader-key! (&rest args)
|
||||
"Define <localleader> key.
|
||||
|
||||
Uses `general-define-key' under the hood, but does not support :major-modes,
|
||||
:states, :prefix or :non-normal-prefix. Use `map!' for a more convenient
|
||||
interface.
|
||||
|
||||
See `doom-localleader-key' and `doom-localleader-alt-key' to change the
|
||||
localleader prefix."
|
||||
(if (featurep 'evil)
|
||||
;; :non-normal-prefix doesn't apply to non-evil sessions (only evil's
|
||||
;; emacs state)
|
||||
`(general-define-key
|
||||
:states '(normal visual motion emacs insert)
|
||||
:major-modes t
|
||||
:prefix doom-localleader-key
|
||||
:non-normal-prefix doom-localleader-alt-key
|
||||
,@args)
|
||||
`(general-define-key
|
||||
:major-modes t
|
||||
:prefix doom-localleader-alt-key
|
||||
,@args)))
|
||||
|
||||
;; We use a prefix commands instead of general's :prefix/:non-normal-prefix
|
||||
;; properties because general is incredibly slow binding keys en mass with them
|
||||
;; in conjunction with :states -- an effective doubling of Doom's startup time!
|
||||
(define-prefix-command 'doom/leader 'doom-leader-map)
|
||||
(define-key doom-leader-map [override-state] 'all)
|
||||
|
||||
;; Bind `doom-leader-key' and `doom-leader-alt-key' as late as possible to give
|
||||
;; the user a chance to modify them.
|
||||
(add-hook! 'doom-after-init-modules-hook
|
||||
(defun doom-init-leader-keys-h ()
|
||||
"Bind `doom-leader-key' and `doom-leader-alt-key'."
|
||||
(let ((map general-override-mode-map))
|
||||
(if (not (featurep 'evil))
|
||||
(progn
|
||||
(cond ((equal doom-leader-alt-key "C-c")
|
||||
(set-keymap-parent doom-leader-map mode-specific-map))
|
||||
((equal doom-leader-alt-key "C-x")
|
||||
(set-keymap-parent doom-leader-map ctl-x-map)))
|
||||
(define-key map (kbd doom-leader-alt-key) 'doom/leader))
|
||||
(evil-define-key* '(normal visual motion) map (kbd doom-leader-key) 'doom/leader)
|
||||
(evil-define-key* '(emacs insert) map (kbd doom-leader-alt-key) 'doom/leader))
|
||||
(general-override-mode +1))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Packages
|
||||
|
||||
(use-package! which-key
|
||||
:hook (doom-first-input . which-key-mode)
|
||||
:init
|
||||
(setq which-key-sort-order #'which-key-key-order-alpha
|
||||
which-key-sort-uppercase-first nil
|
||||
which-key-add-column-padding 1
|
||||
which-key-max-display-columns nil
|
||||
which-key-min-display-lines 6
|
||||
which-key-side-window-slot -10)
|
||||
:config
|
||||
(defvar doom--initial-which-key-replacement-alist which-key-replacement-alist)
|
||||
(add-hook! 'doom-before-reload-hook
|
||||
(defun doom-reset-which-key-replacements-h ()
|
||||
(setq which-key-replacement-alist doom--initial-which-key-replacement-alist)))
|
||||
;; general improvements to which-key readability
|
||||
(which-key-setup-side-window-bottom)
|
||||
(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>"))
|
||||
|
||||
|
||||
;;
|
||||
;;; `map!' macro
|
||||
|
||||
(defvar doom-evil-state-alist
|
||||
'((?n . normal)
|
||||
(?v . visual)
|
||||
(?i . insert)
|
||||
(?e . emacs)
|
||||
(?o . operator)
|
||||
(?m . motion)
|
||||
(?r . replace)
|
||||
(?g . global))
|
||||
"A list of cons cells that map a letter to a evil state symbol.")
|
||||
|
||||
(defun doom--map-keyword-to-states (keyword)
|
||||
"Convert a KEYWORD into a list of evil state symbols.
|
||||
|
||||
For example, :nvi will map to (list 'normal 'visual 'insert). See
|
||||
`doom-evil-state-alist' to customize this."
|
||||
(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)))
|
||||
|
||||
|
||||
;; specials
|
||||
(defvar doom--map-forms nil)
|
||||
(defvar doom--map-fn nil)
|
||||
(defvar doom--map-batch-forms nil)
|
||||
(defvar doom--map-state '(:dummy t))
|
||||
(defvar doom--map-parent-state nil)
|
||||
(defvar doom--map-evil-p nil)
|
||||
(after! evil (setq doom--map-evil-p t))
|
||||
|
||||
(defun doom--map-process (rest)
|
||||
(let ((doom--map-fn doom--map-fn)
|
||||
doom--map-state
|
||||
doom--map-forms
|
||||
desc)
|
||||
(while rest
|
||||
(let ((key (pop rest)))
|
||||
(cond ((listp key)
|
||||
(doom--map-nested nil key))
|
||||
|
||||
((keywordp key)
|
||||
(pcase key
|
||||
(:leader
|
||||
(doom--map-commit)
|
||||
(setq doom--map-fn 'doom--define-leader-key))
|
||||
(:localleader
|
||||
(doom--map-commit)
|
||||
(setq doom--map-fn 'define-localleader-key!))
|
||||
(:after
|
||||
(doom--map-nested (list 'after! (pop rest)) rest)
|
||||
(setq rest nil))
|
||||
(:desc
|
||||
(setq desc (pop rest)))
|
||||
(:map
|
||||
(doom--map-set :keymaps `(quote ,(doom-enlist (pop rest)))))
|
||||
(:mode
|
||||
(push (cl-loop for m in (doom-enlist (pop rest))
|
||||
collect (intern (concat (symbol-name m) "-map")))
|
||||
rest)
|
||||
(push :map rest))
|
||||
((or :when :unless)
|
||||
(doom--map-nested (list (intern (doom-keyword-name key)) (pop rest)) rest)
|
||||
(setq rest nil))
|
||||
(:prefix-map
|
||||
(cl-destructuring-bind (prefix . desc)
|
||||
(doom-enlist (pop rest))
|
||||
(let ((keymap (intern (format "doom-leader-%s-map" desc))))
|
||||
(setq rest
|
||||
(append (list :desc desc prefix keymap
|
||||
:prefix prefix)
|
||||
rest))
|
||||
(push `(defvar ,keymap (make-sparse-keymap))
|
||||
doom--map-forms))))
|
||||
(:prefix
|
||||
(cl-destructuring-bind (prefix . desc)
|
||||
(doom-enlist (pop rest))
|
||||
(doom--map-set (if doom--map-fn :infix :prefix)
|
||||
prefix)
|
||||
(when (stringp desc)
|
||||
(setq rest (append (list :desc desc "" nil) rest)))))
|
||||
(:textobj
|
||||
(let* ((key (pop rest))
|
||||
(inner (pop rest))
|
||||
(outer (pop rest)))
|
||||
(push `(map! (:map evil-inner-text-objects-map ,key ,inner)
|
||||
(:map evil-outer-text-objects-map ,key ,outer))
|
||||
doom--map-forms)))
|
||||
(_
|
||||
(condition-case _
|
||||
(doom--map-def (pop rest) (pop rest)
|
||||
(doom--map-keyword-to-states key)
|
||||
desc)
|
||||
(error
|
||||
(error "Not a valid `map!' property: %s" key)))
|
||||
(setq desc nil))))
|
||||
|
||||
((doom--map-def key (pop rest) nil desc)
|
||||
(setq desc nil)))))
|
||||
|
||||
(doom--map-commit)
|
||||
(macroexp-progn (nreverse (delq nil doom--map-forms)))))
|
||||
|
||||
(defun doom--map-append-keys (prop)
|
||||
(let ((a (plist-get doom--map-parent-state prop))
|
||||
(b (plist-get doom--map-state prop)))
|
||||
(if (and a b)
|
||||
`(general--concat t ,a ,b)
|
||||
(or a b))))
|
||||
|
||||
(defun doom--map-nested (wrapper rest)
|
||||
(doom--map-commit)
|
||||
(let ((doom--map-parent-state (doom--map-state)))
|
||||
(push (if wrapper
|
||||
(append wrapper (list (doom--map-process rest)))
|
||||
(doom--map-process rest))
|
||||
doom--map-forms)))
|
||||
|
||||
(defun doom--map-set (prop &optional value)
|
||||
(unless (equal (plist-get doom--map-state prop) value)
|
||||
(doom--map-commit))
|
||||
(setq doom--map-state (plist-put doom--map-state prop value)))
|
||||
|
||||
(defun doom--map-def (key def &optional states desc)
|
||||
(when (or (memq 'global states)
|
||||
(null states))
|
||||
(setq states (cons 'nil (delq 'global states))))
|
||||
(when desc
|
||||
(let (unquoted)
|
||||
(cond ((and (listp def)
|
||||
(keywordp (car-safe (setq unquoted (doom-unquote def)))))
|
||||
(setq def (list 'quote (plist-put unquoted :which-key desc))))
|
||||
((setq def (cons 'list
|
||||
(if (and (equal key "")
|
||||
(null def))
|
||||
`(:ignore t :which-key ,desc)
|
||||
(plist-put (general--normalize-extended-def def)
|
||||
:which-key desc))))))))
|
||||
(dolist (state states)
|
||||
(push (list key def)
|
||||
(alist-get state doom--map-batch-forms)))
|
||||
t)
|
||||
|
||||
(defun doom--map-commit ()
|
||||
(when doom--map-batch-forms
|
||||
(cl-loop with attrs = (doom--map-state)
|
||||
for (state . defs) in doom--map-batch-forms
|
||||
if (or doom--map-evil-p (not state))
|
||||
collect `(,(or doom--map-fn 'general-define-key)
|
||||
,@(if state `(:states ',state)) ,@attrs
|
||||
,@(mapcan #'identity (nreverse defs)))
|
||||
into forms
|
||||
finally do (push (macroexp-progn forms) doom--map-forms))
|
||||
(setq doom--map-batch-forms nil)))
|
||||
|
||||
(defun doom--map-state ()
|
||||
(let ((plist
|
||||
(append (list :prefix (doom--map-append-keys :prefix)
|
||||
:infix (doom--map-append-keys :infix)
|
||||
:keymaps
|
||||
(append (plist-get doom--map-parent-state :keymaps)
|
||||
(plist-get doom--map-state :keymaps)))
|
||||
doom--map-state
|
||||
nil))
|
||||
newplist)
|
||||
(while plist
|
||||
(let ((key (pop plist))
|
||||
(val (pop plist)))
|
||||
(when (and val (not (plist-member newplist key)))
|
||||
(push val newplist)
|
||||
(push key newplist))))
|
||||
newplist))
|
||||
|
||||
;;
|
||||
(defmacro map! (&rest rest)
|
||||
"A convenience macro for defining keybinds, powered by `general'.
|
||||
|
||||
If evil isn't loaded, evil-specific bindings are ignored.
|
||||
|
||||
Properties
|
||||
:leader [...] an alias for (:prefix doom-leader-key ...)
|
||||
:localleader [...] bind to localleader; requires a keymap
|
||||
:mode [MODE(s)] [...] inner keybinds are applied to major MODE(s)
|
||||
:map [KEYMAP(s)] [...] inner keybinds are applied to KEYMAP(S)
|
||||
:prefix [PREFIX] [...] set keybind prefix for following keys. PREFIX
|
||||
can be a cons cell: (PREFIX . DESCRIPTION)
|
||||
:prefix-map [PREFIX] [...] same as :prefix, but defines a prefix keymap
|
||||
where the following keys will be bound. DO NOT
|
||||
USE THIS IN YOUR PRIVATE CONFIG.
|
||||
:after [FEATURE] [...] apply keybinds when [FEATURE] loads
|
||||
:textobj KEY INNER-FN OUTER-FN define a text object keybind pair
|
||||
:when [CONDITION] [...]
|
||||
:unless [CONDITION] [...]
|
||||
|
||||
Any of the above properties may be nested, so that they only apply to a
|
||||
certain group of keybinds.
|
||||
|
||||
States
|
||||
:n normal
|
||||
:v visual
|
||||
:i insert
|
||||
:e emacs
|
||||
:o operator
|
||||
:m motion
|
||||
:r replace
|
||||
:g global (binds the key without evil `current-global-map')
|
||||
|
||||
These can be combined in any order, e.g. :nvi will apply to normal, visual and
|
||||
insert mode. The state resets after the following key=>def pair. If states are
|
||||
omitted the keybind will be global (no emacs state; this is different from
|
||||
evil's Emacs state and will work in the absence of `evil-mode').
|
||||
|
||||
These must be placed right before the key string.
|
||||
|
||||
Do
|
||||
(map! :leader :desc \"Description\" :n \"C-c\" #'dosomething)
|
||||
Don't
|
||||
(map! :n :leader :desc \"Description\" \"C-c\" #'dosomething)
|
||||
(map! :leader :n :desc \"Description\" \"C-c\" #'dosomething)"
|
||||
(doom--map-process rest))
|
||||
|
||||
(provide 'core-keybinds)
|
||||
;;; core-keybinds.el ends here
|
|
@ -0,0 +1,688 @@
|
|||
;;; core-lib.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Helpers
|
||||
|
||||
(defun doom--resolve-hook-forms (hooks)
|
||||
"Converts a list of modes into a list of hook symbols.
|
||||
|
||||
If a mode is quoted, it is left as is. If the entire HOOKS list is quoted, the
|
||||
list is returned as-is."
|
||||
(declare (pure t) (side-effect-free t))
|
||||
(let ((hook-list (doom-enlist (doom-unquote hooks))))
|
||||
(if (eq (car-safe hooks) 'quote)
|
||||
hook-list
|
||||
(cl-loop for hook in hook-list
|
||||
if (eq (car-safe hook) 'quote)
|
||||
collect (cadr hook)
|
||||
else collect (intern (format "%s-hook" (symbol-name hook)))))))
|
||||
|
||||
(defun doom--setq-hook-fns (hooks rest &optional singles)
|
||||
(unless (or singles (= 0 (% (length rest) 2)))
|
||||
(signal 'wrong-number-of-arguments (list #'evenp (length rest))))
|
||||
(cl-loop with vars = (let ((args rest)
|
||||
vars)
|
||||
(while args
|
||||
(push (if singles
|
||||
(list (pop args))
|
||||
(cons (pop args) (pop args)))
|
||||
vars))
|
||||
(nreverse vars))
|
||||
for hook in (doom--resolve-hook-forms hooks)
|
||||
for mode = (string-remove-suffix "-hook" (symbol-name hook))
|
||||
append
|
||||
(cl-loop for (var . val) in vars
|
||||
collect
|
||||
(list var val hook
|
||||
(intern (format "doom--setq-%s-for-%s-h"
|
||||
var mode))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Public library
|
||||
|
||||
(defun doom-unquote (exp)
|
||||
"Return EXP unquoted."
|
||||
(declare (pure t) (side-effect-free t))
|
||||
(while (memq (car-safe exp) '(quote function))
|
||||
(setq exp (cadr exp)))
|
||||
exp)
|
||||
|
||||
(defun doom-enlist (exp)
|
||||
"Return EXP wrapped in a list, or as-is if already a list."
|
||||
(declare (pure t) (side-effect-free t))
|
||||
(if (listp exp) exp (list exp)))
|
||||
|
||||
(defun doom-keyword-intern (str)
|
||||
"Converts STR (a string) into a keyword (`keywordp')."
|
||||
(declare (pure t) (side-effect-free t))
|
||||
(cl-check-type str string)
|
||||
(intern (concat ":" str)))
|
||||
|
||||
(defun doom-keyword-name (keyword)
|
||||
"Returns the string name of KEYWORD (`keywordp') minus the leading colon."
|
||||
(declare (pure t) (side-effect-free t))
|
||||
(cl-check-type keyword keyword)
|
||||
(substring (symbol-name keyword) 1))
|
||||
|
||||
(defmacro doom-log (format-string &rest args)
|
||||
"Log to *Messages* if `doom-debug-p' is on.
|
||||
Does not display text in echo area, but still logs to *Messages*. Accepts the
|
||||
same arguments as `message'."
|
||||
`(when doom-debug-p
|
||||
(let ((inhibit-message (active-minibuffer-window)))
|
||||
(message
|
||||
,(concat (propertize "DOOM " 'face 'font-lock-comment-face)
|
||||
(when (bound-and-true-p doom--current-module)
|
||||
(propertize
|
||||
(format "[%s/%s] "
|
||||
(doom-keyword-name (car doom--current-module))
|
||||
(cdr doom--current-module))
|
||||
'face 'warning))
|
||||
format-string)
|
||||
,@args))))
|
||||
|
||||
(defalias 'doom-partial #'apply-partially)
|
||||
|
||||
(defun doom-rpartial (fn &rest args)
|
||||
"Return a partial application of FUN to right-hand ARGS.
|
||||
|
||||
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))))
|
||||
|
||||
(defun doom-lookup-key (keys &optional keymap)
|
||||
"Like `lookup-key', but search active keymaps if KEYMAP is omitted."
|
||||
(if keymap
|
||||
(lookup-key keymap keys)
|
||||
(cl-loop for keymap
|
||||
in (append (cl-loop for alist in emulation-mode-map-alists
|
||||
append (mapcar #'cdr
|
||||
(if (symbolp alist)
|
||||
(if (boundp alist) (symbol-value alist))
|
||||
alist)))
|
||||
(list (current-local-map))
|
||||
(mapcar #'cdr minor-mode-overriding-map-alist)
|
||||
(mapcar #'cdr minor-mode-map-alist)
|
||||
(list (current-global-map)))
|
||||
if (keymapp keymap)
|
||||
if (lookup-key keymap keys)
|
||||
return it)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Sugars
|
||||
|
||||
(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."
|
||||
(cond ((bound-and-true-p byte-compile-current-file))
|
||||
(load-file-name)
|
||||
((stringp (car-safe current-load-list))
|
||||
(car current-load-list))
|
||||
(buffer-file-name)
|
||||
((error "Cannot get this file-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.
|
||||
Intended as a simpler version of `cl-letf' and `cl-macrolet'.
|
||||
|
||||
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'. In interactive sessions this won't suppress writing
|
||||
to *Messages*, only inhibit output in the echo area."
|
||||
`(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 eval-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 eval-when! (cond &rest body)
|
||||
"Expands to BODY if CONDITION is non-nil at compile/expansion time.
|
||||
See `eval-if!' for details on this macro's purpose."
|
||||
(declare (indent 1))
|
||||
(when (eval cond)
|
||||
(macroexp-progn body)))
|
||||
|
||||
|
||||
;;; Closure factories
|
||||
(defmacro fn! (arglist &rest body)
|
||||
"Returns (cl-function (lambda ARGLIST BODY...))
|
||||
The closure is wrapped in `cl-function', meaning ARGLIST will accept anything
|
||||
`cl-defun' will. "
|
||||
(declare (indent defun) (doc-string 1) (pure t) (side-effect-free t))
|
||||
`(cl-function (lambda ,arglist ,@body)))
|
||||
|
||||
(defmacro cmd! (&rest body)
|
||||
"Returns (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 &optional prefix-arg &rest args)
|
||||
"Returns a closure that interactively calls COMMAND with ARGS and PREFIX-ARG.
|
||||
Like `cmd!', but allows you to change `current-prefix-arg' or pass arguments to
|
||||
COMMAND. This macro is meant to be used as a target for keybinds (e.g. with
|
||||
`define-key' or `map!')."
|
||||
(declare (doc-string 1) (pure t) (side-effect-free t))
|
||||
`(lambda (arg &rest _) (interactive "P")
|
||||
(let ((current-prefix-arg (or ,prefix-arg arg)))
|
||||
(,(if args
|
||||
'funcall-interactively
|
||||
'call-interactively)
|
||||
,command ,@args))))
|
||||
|
||||
(defmacro cmds! (&rest branches)
|
||||
"Returns a dispatcher that runs the a command in BRANCHES.
|
||||
Meant to be used as a target for keybinds (e.g. with `define-key' or `map!').
|
||||
|
||||
BRANCHES is a flat list of CONDITION COMMAND pairs. CONDITION is a lisp form
|
||||
that is evaluated when (and each time) the dispatcher is invoked. If it returns
|
||||
non-nil, COMMAND is invoked, otherwise it falls through to the next pair.
|
||||
|
||||
The last element of BRANCHES can be a COMMANd with no CONDITION. This acts as
|
||||
the fallback if all other conditions fail.
|
||||
|
||||
Otherwise, Emacs will fall through the keybind and search the next keymap for a
|
||||
keybind (as if this keybind never existed).
|
||||
|
||||
See `general-key-dispatch' for what other arguments it accepts in BRANCHES."
|
||||
(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)))
|
||||
|
||||
(defalias 'kbd! 'general-simulate-key)
|
||||
|
||||
;; 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'.
|
||||
|
||||
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'."
|
||||
(let ((var (make-symbol "result")))
|
||||
`(dolist (,var (list ,@values) (with-no-warnings ,place))
|
||||
(cl-pushnew ,var ,place :test #'equal))))
|
||||
|
||||
(defmacro prependq! (sym &rest lists)
|
||||
"Prepend LISTS to SYM in place."
|
||||
`(setq ,sym (append ,@lists ,sym)))
|
||||
|
||||
|
||||
;;; 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."
|
||||
`(let ((default-directory ,(dir!))
|
||||
file-name-handler-alist)
|
||||
(dolist (dir (list ,@dirs))
|
||||
(cl-pushnew (expand-file-name dir) load-path :test #'string=))))
|
||||
|
||||
(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.
|
||||
|
||||
FORMS are evaluated once, when that function/hook is first invoked, then never
|
||||
again.
|
||||
|
||||
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)))
|
||||
;; 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))
|
||||
((symbolp sym) (remove-hook sym #',fn))))
|
||||
(unintern ',fn nil))
|
||||
(cond ((functionp sym)
|
||||
(advice-add ,hook-or-function ,(if append :after :before) #',fn))
|
||||
((symbolp sym)
|
||||
(put ',fn 'permanent-local-hook t)
|
||||
(add-hook sym #',fn ,append))))))
|
||||
|
||||
(defmacro add-hook! (hooks &rest rest)
|
||||
"A convenience macro for adding N functions to M hooks.
|
||||
|
||||
This macro accepts, in order:
|
||||
|
||||
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),
|
||||
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)
|
||||
(goto-char indent-point)
|
||||
(when (looking-at-p "\\s-*(")
|
||||
(lisp-indent-defform state indent-point))))
|
||||
(debug t))
|
||||
(let* ((hook-forms (doom--resolve-hook-forms hooks))
|
||||
(func-forms ())
|
||||
(defn-forms ())
|
||||
append-p
|
||||
local-p
|
||||
remove-p
|
||||
forms)
|
||||
(while (keywordp (car rest))
|
||||
(pcase (pop rest)
|
||||
(:append (setq append-p t))
|
||||
(:local (setq local-p t))
|
||||
(:remove (setq remove-p t))))
|
||||
(let ((first (car-safe (car rest))))
|
||||
(cond ((null first)
|
||||
(setq func-forms rest))
|
||||
|
||||
((eq first 'defun)
|
||||
(setq func-forms (mapcar #'cadr rest)
|
||||
defn-forms rest))
|
||||
|
||||
((memq first '(quote function))
|
||||
(setq func-forms
|
||||
(if (cdr rest)
|
||||
(mapcar #'doom-unquote rest)
|
||||
(doom-enlist (doom-unquote (car rest))))))
|
||||
|
||||
((setq func-forms (list `(lambda (&rest _) ,@rest)))))
|
||||
(dolist (hook hook-forms)
|
||||
(dolist (func func-forms)
|
||||
(push (if remove-p
|
||||
`(remove-hook ',hook #',func ,local-p)
|
||||
`(add-hook ',hook #',func ,append-p ,local-p))
|
||||
forms)))
|
||||
(macroexp-progn
|
||||
(append defn-forms
|
||||
(if append-p
|
||||
(nreverse forms)
|
||||
forms))))))
|
||||
|
||||
(defmacro remove-hook! (hooks &rest rest)
|
||||
"A convenience macro for removing N functions from M hooks.
|
||||
|
||||
Takes the same arguments as `add-hook!'.
|
||||
|
||||
If N and M = 1, there's no benefit to using this macro over `remove-hook'.
|
||||
|
||||
\(fn HOOKS [:append :local] FUNCTIONS)"
|
||||
(declare (indent defun) (debug t))
|
||||
`(add-hook! ,hooks :remove ,@rest))
|
||||
|
||||
(defmacro setq-hook! (hooks &rest var-vals)
|
||||
"Sets buffer-local variables on HOOKS.
|
||||
|
||||
\(fn HOOKS &rest [SYM VAL]...)"
|
||||
(declare (indent 1))
|
||||
(macroexp-progn
|
||||
(cl-loop for (var val hook fn) in (doom--setq-hook-fns hooks var-vals)
|
||||
collect `(defun ,fn (&rest _)
|
||||
,(format "%s = %s" var (pp-to-string val))
|
||||
(setq-local ,var ,val))
|
||||
collect `(remove-hook ',hook #',fn) ; ensure set order
|
||||
collect `(add-hook ',hook #',fn))))
|
||||
|
||||
(defmacro unsetq-hook! (hooks &rest vars)
|
||||
"Unbind setq hooks on HOOKS for VARS.
|
||||
|
||||
\(fn HOOKS &rest [SYM VAL]...)"
|
||||
(declare (indent 1))
|
||||
(macroexp-progn
|
||||
(cl-loop for (_var _val hook fn)
|
||||
in (doom--setq-hook-fns hooks vars 'singles)
|
||||
collect `(remove-hook ',hook #',fn))))
|
||||
|
||||
|
||||
;;; Definers
|
||||
(defmacro defadvice! (symbol arglist &optional docstring &rest body)
|
||||
"Define an advice called SYMBOL and add it to PLACES.
|
||||
|
||||
ARGLIST is as in `defun'. WHERE is a keyword as passed to `advice-add', and
|
||||
PLACE is the function to which to add the advice, like in `advice-add'.
|
||||
DOCSTRING and BODY are as in `defun'.
|
||||
|
||||
\(fn SYMBOL ARGLIST &optional DOCSTRING &rest [WHERE PLACES...] BODY\)"
|
||||
(declare (doc-string 3) (indent defun))
|
||||
(unless (stringp docstring)
|
||||
(push docstring body)
|
||||
(setq docstring nil))
|
||||
(let (where-alist)
|
||||
(while (keywordp (car body))
|
||||
(push `(cons ,(pop body) (doom-enlist ,(pop body)))
|
||||
where-alist))
|
||||
`(progn
|
||||
(defun ,symbol ,arglist ,docstring ,@body)
|
||||
(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
|
||||
|
||||
(eval-when! (version< emacs-version "27.0.90")
|
||||
;; 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)))))
|
||||
|
||||
(eval-when! (version< emacs-version "27.1")
|
||||
;; DEPRECATED Backported from Emacs 27; earlier verisons don't have REMOTE arg
|
||||
(defun executable-find (command &optional remote)
|
||||
"Search for COMMAND in `exec-path' and return the absolute file name.
|
||||
Return nil if COMMAND is not found anywhere in `exec-path'. If
|
||||
REMOTE is non-nil, search on the remote host indicated by
|
||||
`default-directory' instead."
|
||||
(if (and remote (file-remote-p default-directory))
|
||||
(let ((res (locate-file
|
||||
command
|
||||
(mapcar
|
||||
(lambda (x) (concat (file-remote-p default-directory) x))
|
||||
(exec-path))
|
||||
exec-suffixes 'file-executable-p)))
|
||||
(when (stringp res) (file-local-name res)))
|
||||
;; Use 1 rather than file-executable-p to better match the
|
||||
;; behavior of call-process.
|
||||
(let ((default-directory
|
||||
(let (file-name-handler-alist)
|
||||
(file-name-quote default-directory))))
|
||||
(locate-file command exec-path exec-suffixes 1)))))
|
||||
|
||||
(unless (fboundp 'exec-path)
|
||||
;; DEPRECATED Backported from Emacs 27.1
|
||||
(defun exec-path ()
|
||||
"Return list of directories to search programs to run in remote subprocesses.
|
||||
The remote host is identified by `default-directory'. For remote
|
||||
hosts that do not support subprocesses, this returns `nil'.
|
||||
If `default-directory' is a local directory, this function returns
|
||||
the value of the variable `exec-path'."
|
||||
(let ((handler (find-file-name-handler default-directory 'exec-path)))
|
||||
(if handler
|
||||
(funcall handler 'exec-path)
|
||||
exec-path))))
|
||||
|
||||
(provide 'core-lib)
|
||||
;;; core-lib.el ends here
|
|
@ -0,0 +1,570 @@
|
|||
;;; core-modules.el --- module & package management system -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-init-modules-p nil
|
||||
"Non-nil if `doom-initialize-modules' has run.")
|
||||
|
||||
(defvar doom-modules (make-hash-table :test 'equal)
|
||||
"A hash table of enabled modules. Set by `doom-initialize-modules'.")
|
||||
|
||||
(defvar doom-modules-dirs
|
||||
(list (expand-file-name "modules/" doom-private-dir)
|
||||
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 (: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))
|
||||
(macos (:os macos)))
|
||||
(: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))
|
||||
(pretty-code (:ui ligatures)))
|
||||
(: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 (:checkers spell))
|
||||
(syntax-checker (:tools flycheck)))
|
||||
|
||||
This marks :feature version-control, :feature spellcheck and :feature
|
||||
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-p
|
||||
"If non-nil, don't emit deprecated or missing module warnings at startup.")
|
||||
|
||||
;;; Custom hooks
|
||||
(defvar doom-before-init-modules-hook nil
|
||||
"A list of hooks to run before Doom's modules' config.el files are loaded, but
|
||||
after their init.el files are loaded.")
|
||||
|
||||
(defvar doom-init-modules-hook nil
|
||||
"A list of hooks to run after Doom's modules' config.el files have loaded, but
|
||||
before the user's private module.")
|
||||
|
||||
(defvaralias 'doom-after-init-modules-hook 'after-init-hook)
|
||||
|
||||
(defvar doom--current-module nil)
|
||||
(defvar doom--current-flags nil)
|
||||
|
||||
|
||||
;;
|
||||
;;; Bootstrap API
|
||||
|
||||
(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)
|
||||
(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)
|
||||
(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)
|
||||
(load custom-file 'noerror (not doom-debug-mode))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Module API
|
||||
|
||||
(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))
|
||||
(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."
|
||||
(declare (pure t) (side-effect-free t))
|
||||
(when-let (plist (gethash (cons category module) doom-modules))
|
||||
(if property
|
||||
(plist-get plist property)
|
||||
plist)))
|
||||
|
||||
(defun doom-module-put (category module &rest plist)
|
||||
"Set a PROPERTY for CATEGORY MODULE to VALUE. PLIST should be additional pairs
|
||||
of PROPERTY and VALUEs.
|
||||
|
||||
\(fn CATEGORY MODULE PROPERTY VALUE &rest [PROPERTY VALUE [...]])"
|
||||
(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'.
|
||||
|
||||
CATEGORY is a keyword, module is a symbol, PLIST is a plist that accepts the
|
||||
following properties:
|
||||
|
||||
:flags [SYMBOL LIST] list of enabled category flags
|
||||
:path [STRING] path to category root directory
|
||||
|
||||
Example:
|
||||
(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
|
||||
MODULE (symbol).
|
||||
|
||||
If the category isn't enabled this will always return nil. For finding disabled
|
||||
modules use `doom-module-locate-path'."
|
||||
(let ((path (doom-module-get category module :path)))
|
||||
(if file
|
||||
(let (file-name-handler-alist)
|
||||
(expand-file-name file path))
|
||||
path)))
|
||||
|
||||
(defun doom-module-locate-path (category &optional module file)
|
||||
"Searches `doom-modules-dirs' to find the path to a module.
|
||||
|
||||
CATEGORY is a keyword (e.g. :lang) and MODULE is a symbol (e.g. 'python). FILE
|
||||
is a string that will be appended to the resulting path. If no path exists, this
|
||||
returns nil, otherwise an absolute path.
|
||||
|
||||
This doesn't require modules to be enabled. For enabled modules us
|
||||
`doom-module-path'."
|
||||
(when (keywordp category)
|
||||
(setq category (doom-keyword-name category)))
|
||||
(when (and module (symbolp module))
|
||||
(setq module (symbol-name module)))
|
||||
(cl-loop with file-name-handler-alist = nil
|
||||
for default-directory in doom-modules-dirs
|
||||
for path = (concat category "/" module "/" file)
|
||||
if (file-exists-p path)
|
||||
return (expand-file-name path)))
|
||||
|
||||
(defun doom-module-from-path (&optional path enabled-only)
|
||||
"Returns a cons cell (CATEGORY . MODULE) derived from PATH (a file path).
|
||||
If ENABLED-ONLY, return nil if the containing module isn't enabled."
|
||||
(if (null path)
|
||||
(if doom--current-module
|
||||
(if enabled-only
|
||||
(and (doom-module-p (car doom--current-module)
|
||||
(cdr doom--current-module))
|
||||
doom--current-module)
|
||||
doom--current-module)
|
||||
(ignore-errors
|
||||
(doom-module-from-path (file!))))
|
||||
(let* ((file-name-handler-alist nil)
|
||||
(path (expand-file-name (or path (file!)))))
|
||||
(save-match-data
|
||||
(cond ((string-match "/modules/\\([^/]+\\)/\\([^/]+\\)\\(?:/.*\\)?$" path)
|
||||
(when-let* ((category (doom-keyword-intern (match-string 1 path)))
|
||||
(module (intern (match-string 2 path))))
|
||||
(and (or (null enabled-only)
|
||||
(doom-module-p category module))
|
||||
(cons category module))))
|
||||
((or (string-match-p (concat "^" (regexp-quote doom-core-dir)) path)
|
||||
(file-in-directory-p path doom-core-dir))
|
||||
(cons :core (intern (file-name-base path))))
|
||||
((or (string-match-p (concat "^" (regexp-quote doom-private-dir)) path)
|
||||
(file-in-directory-p path doom-private-dir))
|
||||
(cons :private (intern (file-name-base path)))))))))
|
||||
|
||||
(defun doom-module-load-path (&optional module-dirs)
|
||||
"Return a list of file paths to activated modules.
|
||||
|
||||
The list is in no particular order and its file paths are absolute. If
|
||||
MODULE-DIRS is non-nil, include all modules (even disabled ones) available in
|
||||
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
|
||||
(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)))
|
||||
(delq
|
||||
nil (cl-loop for plist being the hash-values of doom-modules
|
||||
collect (plist-get plist :path)) ))
|
||||
nil))
|
||||
|
||||
(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."
|
||||
(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
|
||||
|
||||
(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
|
||||
(lambda (name &rest _)
|
||||
(message "Ignoring ':ensure t' in '%s' config" name)))
|
||||
;; ...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
|
||||
;; `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
|
||||
;; so we don't have to load `auto-minor-mode' so early.
|
||||
(dolist (keyword '(:minor :magic-minor))
|
||||
(setq use-package-keywords
|
||||
(use-package-list-insert keyword use-package-keywords :commands)))
|
||||
|
||||
(defalias 'use-package-normalize/:minor #'use-package-normalize-mode)
|
||||
(defun use-package-handler/:minor (name _ arg rest state)
|
||||
(use-package-handle-mode name 'auto-minor-mode-alist arg rest state))
|
||||
|
||||
(defalias 'use-package-normalize/:magic-minor #'use-package-normalize-mode)
|
||||
(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
|
||||
;; :defer-incrementally SYMBOL|LIST|t
|
||||
;;
|
||||
;; Check out `use-package!'s documentation for more about these two.
|
||||
(dolist (keyword '(:defer-incrementally :after-call))
|
||||
(push keyword use-package-deferring-keywords)
|
||||
(setq use-package-keywords
|
||||
(use-package-list-insert keyword use-package-keywords :after)))
|
||||
|
||||
(defalias 'use-package-normalize/:defer-incrementally #'use-package-normalize-symlist)
|
||||
(defun use-package-handler/:defer-incrementally (name _keyword targets rest state)
|
||||
(use-package-concat
|
||||
`((doom-load-packages-incrementally
|
||||
',(if (equal targets '(t))
|
||||
(list name)
|
||||
(append targets (list name)))))
|
||||
(use-package-process-keywords name rest state)))
|
||||
|
||||
(defalias 'use-package-normalize/:after-call #'use-package-normalize-symlist)
|
||||
(defun use-package-handler/:after-call (name _keyword hooks rest state)
|
||||
(if (plist-get state :demand)
|
||||
(use-package-process-keywords name rest state)
|
||||
(let ((fn (make-symbol (format "doom--after-call-%s-h" name))))
|
||||
(use-package-concat
|
||||
`((fset ',fn
|
||||
(lambda (&rest _)
|
||||
(doom-log "Loading deferred package %s from %s" ',name ',fn)
|
||||
(condition-case e
|
||||
;; If `default-directory' is a directory that doesn't
|
||||
;; exist or is unreadable, Emacs throws up file-missing
|
||||
;; errors, so we set it to a directory we know exists and
|
||||
;; is readable.
|
||||
(let ((default-directory doom-emacs-dir))
|
||||
(require ',name))
|
||||
((debug error)
|
||||
(message "Failed to load deferred package %s: %s" ',name e)))
|
||||
(when-let (deferral-list (assq ',name doom--deferred-packages-alist))
|
||||
(dolist (hook (cdr deferral-list))
|
||||
(advice-remove hook #',fn)
|
||||
(remove-hook hook #',fn))
|
||||
(delq! deferral-list doom--deferred-packages-alist)
|
||||
(unintern ',fn nil)))))
|
||||
(let (forms)
|
||||
(dolist (hook hooks forms)
|
||||
(push (if (string-match-p "-\\(?:functions\\|hook\\)$" (symbol-name hook))
|
||||
`(add-hook ',hook #',fn)
|
||||
`(advice-add #',hook :before #',fn))
|
||||
forms)))
|
||||
`((unless (assq ',name doom--deferred-packages-alist)
|
||||
(push '(,name) doom--deferred-packages-alist))
|
||||
(nconc (assq ',name doom--deferred-packages-alist)
|
||||
'(,@hooks)))
|
||||
(use-package-process-keywords name rest state))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Module config macros
|
||||
|
||||
(put :if 'lisp-indent-function 2)
|
||||
(put :when 'lisp-indent-function 'defun)
|
||||
(put :unless 'lisp-indent-function 'defun)
|
||||
|
||||
(defmacro doom! (&rest modules)
|
||||
"Bootstraps DOOM Emacs and its modules.
|
||||
|
||||
If the first item in MODULES doesn't satisfy `keywordp', MODULES is evaluated,
|
||||
otherwise, MODULES is a multiple-property list (a plist where each key can have
|
||||
multiple, linear values).
|
||||
|
||||
The bootstrap process involves making sure the essential directories exist, core
|
||||
packages are installed, `doom-autoloads-file' is loaded, `doom-packages-file'
|
||||
cache exists (and is loaded) and, finally, loads your private init.el (which
|
||||
should contain your `doom!' block).
|
||||
|
||||
The overall load order of Doom is as follows:
|
||||
|
||||
~/.emacs.d/init.el
|
||||
~/.emacs.d/core/core.el
|
||||
$DOOMDIR/init.el
|
||||
{$DOOMDIR,~/.emacs.d}/modules/*/*/init.el
|
||||
`doom-before-init-modules-hook'
|
||||
{$DOOMDIR,~/.emacs.d}/modules/*/*/config.el
|
||||
`doom-init-modules-hook'
|
||||
$DOOMDIR/config.el
|
||||
`doom-after-init-modules-hook'
|
||||
`after-init-hook'
|
||||
`emacs-startup-hook'
|
||||
`window-setup-hook'
|
||||
|
||||
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)."
|
||||
`(unless doom-interactive-p
|
||||
(doom-module-mplist-map
|
||||
(lambda (category module &rest plist)
|
||||
(if (plist-member 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)
|
||||
"Declares and configures a package.
|
||||
|
||||
This is a thin wrapper around `use-package', and is ignored if the NAME package
|
||||
is disabled by the user (with `package!').
|
||||
|
||||
See `use-package' to see what properties can be provided. Doom adds support for
|
||||
two extra properties:
|
||||
|
||||
:after-call SYMBOL|LIST
|
||||
Takes a symbol or list of symbols representing functions or hook variables.
|
||||
The first time any of these functions or hooks are executed, the package is
|
||||
loaded.
|
||||
|
||||
:defer-incrementally SYMBOL|LIST|t
|
||||
Takes a symbol or list of symbols representing packages that will be loaded
|
||||
incrementally at startup before this one. This is helpful for large packages
|
||||
like magit or org, which load a lot of dependencies on first load. This lets
|
||||
you load them piece-meal during idle periods, so that when you finally do need
|
||||
the package, it'll load quicker.
|
||||
|
||||
NAME is implicitly added if this property is present and non-nil. No need to
|
||||
specify it. A value of `t' implies NAME."
|
||||
(declare (indent 1))
|
||||
(unless (or (memq name doom-disabled-packages)
|
||||
;; At compile-time, use-package will forcibly load packages to
|
||||
;; prevent compile-time errors. However, if a Doom user has
|
||||
;; disabled packages you get file-missing package errors, so it's
|
||||
;; necessary to check for packages at compile time:
|
||||
(and (bound-and-true-p byte-compile-current-file)
|
||||
(not (locate-library (symbol-name name)))))
|
||||
`(use-package ,name ,@plist)))
|
||||
|
||||
(defmacro use-package-hook! (package when &rest body)
|
||||
"Reconfigures a package's `use-package!' block.
|
||||
|
||||
This macro must be used *before* PACKAGE's `use-package!' block. Often, this
|
||||
means using it from your DOOMDIR/init.el.
|
||||
|
||||
Under the hood, this uses use-package's `use-package-inject-hooks'.
|
||||
|
||||
PACKAGE is a symbol; the package's name.
|
||||
WHEN should be one of the following:
|
||||
:pre-init :post-init :pre-config :post-config
|
||||
|
||||
WARNINGS:
|
||||
- The use of this macro is more often than not a code smell. Use it as last
|
||||
resort. There is almost always a better alternative.
|
||||
- If you are using this solely for :post-config, stop! `after!' is much better.
|
||||
- If :pre-init or :pre-config hooks return nil, the original `use-package!''s
|
||||
:init/:config block (respectively) is overwritten, so remember to have them
|
||||
return non-nil (or exploit that to overwrite Doom's config)."
|
||||
(declare (indent defun))
|
||||
(unless (memq when '(:pre-init :post-init :pre-config :post-config))
|
||||
(error "'%s' isn't a valid hook for use-package-hook!" when))
|
||||
`(progn
|
||||
(setq use-package-inject-hooks t)
|
||||
(add-hook ',(intern (format "use-package--%s--%s-hook"
|
||||
package
|
||||
(substring (symbol-name when) 1)))
|
||||
(lambda () ,@body)
|
||||
'append)))
|
||||
|
||||
(defmacro featurep! (category &optional module flag)
|
||||
"Returns t if CATEGORY MODULE is enabled.
|
||||
|
||||
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
|
||||
~/.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 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))
|
||||
((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))
|
||||
|
||||
(provide 'core-modules)
|
||||
;;; core-modules.el ends here
|
|
@ -0,0 +1,568 @@
|
|||
;;; 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 (nominally) reproducible package
|
||||
;; management system. We use `straight' over `package' because the latter is
|
||||
;; 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 opt-in.
|
||||
;;
|
||||
;; 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 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
|
||||
;; versions. There's also 'bin/doom update' for updating only your packages.
|
||||
;;
|
||||
;; How this works is: the system reads packages.el files located in each
|
||||
;; activated module, your private directory (`doom-private-dir'), and one in
|
||||
;; `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 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-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.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-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
|
||||
;; than pulled, so packages are often out of date with upstream.
|
||||
package-archives
|
||||
(let ((proto (if gnutls-verify-error "https" "http")))
|
||||
(list (cons "gnu" (concat proto "://elpa.gnu.org/packages/"))
|
||||
(cons "melpa" (concat proto "://melpa.org/packages/"))
|
||||
(cons "org" (concat proto "://orgmode.org/elpa/")))))
|
||||
|
||||
;; 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', 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
|
||||
|
||||
(setq straight-base-dir doom-local-dir
|
||||
straight-repository-branch "develop"
|
||||
;; Since byte-code is rarely compatible across different versions of
|
||||
;; Emacs, it's best we build them in separate directories, per emacs
|
||||
;; version.
|
||||
straight-build-dir (format "build-%s" emacs-version)
|
||||
straight-cache-autoloads nil ; we already do this, and better.
|
||||
;; Doom doesn't encourage you to modify packages in place. Disabling this
|
||||
;; 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 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
|
||||
;; Prefix declarations are unneeded bulk added to our autoloads file. Best
|
||||
;; we don't have to deal with them at all.
|
||||
autoload-compute-prefixes nil)
|
||||
|
||||
(with-eval-after-load 'straight
|
||||
;; `let-alist' is built into Emacs 26 and onwards
|
||||
(add-to-list 'straight-built-in-pseudo-packages 'let-alist))
|
||||
|
||||
(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)))
|
||||
|
||||
|
||||
;;
|
||||
;;; 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
|
||||
"--single-branch" "--no-tags"))
|
||||
((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)
|
||||
"--no-tags")
|
||||
(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)
|
||||
"Process all packages, essential and otherwise, if they haven't already been.
|
||||
|
||||
If FORCE-P is non-nil, do it anyway.
|
||||
|
||||
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)
|
||||
(unless package--initialized
|
||||
(error "Failed to initialize package.el")))
|
||||
(when (or force-p (null doom-packages))
|
||||
(doom-log "Initializing straight.el")
|
||||
(setq doom-disabled-packages nil
|
||||
doom-packages (doom-package-list))
|
||||
(let (packages)
|
||||
(dolist (package doom-packages)
|
||||
(cl-destructuring-bind
|
||||
(name &key recipe disable ignore shadow &allow-other-keys) package
|
||||
(if ignore
|
||||
(straight-override-recipe (cons name '(:type built-in)))
|
||||
(if disable
|
||||
(cl-pushnew name doom-disabled-packages)
|
||||
(when shadow
|
||||
(straight-override-recipe (cons shadow '(:local-repo nil)))
|
||||
(let ((site-load-path (copy-sequence doom--initial-load-path))
|
||||
lib)
|
||||
(while (setq
|
||||
lib (locate-library (concat (symbol-name shadow) ".el")
|
||||
nil site-load-path))
|
||||
(let ((lib (directory-file-name (file-name-directory lib))))
|
||||
(setq site-load-path (delete lib site-load-path)
|
||||
load-path (delete lib load-path))))))
|
||||
(when recipe
|
||||
(straight-override-recipe (cons name recipe)))
|
||||
(appendq! packages (cons name (straight--get-dependencies name)))))))
|
||||
(dolist (package (cl-delete-duplicates packages :test #'equal))
|
||||
(straight-register-package package)
|
||||
(let ((name (symbol-name package)))
|
||||
(add-to-list 'load-path (directory-file-name (straight--build-dir name)))
|
||||
(straight--load-package-autoloads name))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; 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* ((recipe (straight-recipes-retrieve package))
|
||||
(plist (doom-plist-merge
|
||||
(plist-get (alist-get package doom-packages) :recipe)
|
||||
(cdr (if (memq (car recipe) '(quote \`))
|
||||
(eval recipe t)
|
||||
recipe)))))
|
||||
(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 (copy-sequence (doom-package-recipe package)))
|
||||
(recipe (if (and (not (plist-member recipe :type))
|
||||
(memq (plist-get recipe :host) '(github gitlab bitbucket)))
|
||||
(plist-put recipe :type 'git)
|
||||
recipe))
|
||||
(repo (if-let (local-repo (plist-get recipe :local-repo))
|
||||
(file-name-nondirectory (directory-file-name local-repo))
|
||||
(ignore-errors (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-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)))
|
||||
|
||||
|
||||
;;; 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-disabled-packages
|
||||
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 populate
|
||||
;; `doom-disabled-packages' disabled packages are seen ASAP, and a
|
||||
;; second time to ensure privately overridden packages are properly
|
||||
;; overwritten.
|
||||
(let (doom-packages)
|
||||
(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-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)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Module package macros
|
||||
|
||||
(cl-defmacro package!
|
||||
(name &rest plist &key built-in recipe ignore _type _pin _disable _shadow)
|
||||
"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
|
||||
populate `doom-packages' with metadata about the packages Doom needs to keep
|
||||
track of.
|
||||
|
||||
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
|
||||
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!'
|
||||
and `after!' blocks.
|
||||
:ignore FORM
|
||||
Do not install this package.
|
||||
: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.
|
||||
:shadow PACKAGE
|
||||
Informs Doom that this package is shadowing a built-in PACKAGE; the original
|
||||
package will be removed from `load-path' to mitigate conflicts, and this new
|
||||
package will satisfy any dependencies on PACKAGE in the future.
|
||||
|
||||
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)))
|
||||
(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; throws an error on invalid properties
|
||||
(condition-case e
|
||||
(when-let (recipe (plist-get plist :recipe))
|
||||
(cl-destructuring-bind
|
||||
(&key local-repo _files _flavor
|
||||
_build _pre-build _post-build _no-byte-compile
|
||||
_no-native-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)))))
|
||||
;; These are the only side-effects of this macro!
|
||||
(setf (alist-get name doom-packages) plist)
|
||||
(if (plist-get plist :disable)
|
||||
(add-to-list 'doom-disabled-packages name)
|
||||
(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
|
||||
(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
|
|
@ -0,0 +1,304 @@
|
|||
;;; core-projects.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar doom-projectile-cache-limit 10000
|
||||
"If any project cache surpasses this many files it is purged when quitting
|
||||
Emacs.")
|
||||
|
||||
(defvar doom-projectile-cache-blacklist '("~" "/tmp" "/")
|
||||
"Directories that should never be cached.")
|
||||
|
||||
(defvar doom-projectile-cache-purge-non-projects nil
|
||||
"If non-nil, non-projects are purged from the cache on `kill-emacs-hook'.")
|
||||
|
||||
(defvar doom-projectile-fd-binary
|
||||
(cl-find-if #'executable-find (list "fdfind" "fd"))
|
||||
"The filename of the `fd' executable. On some distros it's 'fdfind' (ubuntu,
|
||||
debian, and derivatives). On most it's 'fd'.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Packages
|
||||
|
||||
(use-package! projectile
|
||||
: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")
|
||||
;; Auto-discovery is slow to do by default. Better to update the list
|
||||
;; when you need to (`projectile-discover-projects-in-search-path').
|
||||
projectile-auto-discover nil
|
||||
projectile-enable-caching doom-interactive-p
|
||||
projectile-globally-ignored-files '(".DS_Store" "TAGS")
|
||||
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o")
|
||||
projectile-kill-buffers-filter 'kill-only-files
|
||||
projectile-known-projects-file (concat doom-cache-dir "projectile.projects")
|
||||
projectile-ignored-projects '("~/" "/tmp"))
|
||||
|
||||
(global-set-key [remap evil-jump-to-tag] #'projectile-find-tag)
|
||||
(global-set-key [remap find-tag] #'projectile-find-tag)
|
||||
|
||||
:config
|
||||
(projectile-mode +1)
|
||||
|
||||
;; Auto-discovery on `projectile-mode' is slow and premature. Let's defer it
|
||||
;; until it's actually needed. Also clean up non-existing projects too!
|
||||
(add-transient-hook! 'projectile-relevant-known-projects
|
||||
(projectile-cleanup-known-projects)
|
||||
(projectile-discover-projects-in-search-path))
|
||||
|
||||
;; 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
|
||||
(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 "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 '()
|
||||
projectile-project-root-files-top-down-recurring '("Makefile"))
|
||||
|
||||
(push (abbreviate-file-name doom-local-dir) projectile-globally-ignored-directories)
|
||||
|
||||
;; Per-project compilation buffers
|
||||
(setq compilation-buffer-name-function #'projectile-compilation-buffer-name
|
||||
compilation-save-buffers-predicate #'projectile-current-project-buffer-p)
|
||||
|
||||
;; 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.
|
||||
(put 'projectile-ag 'disabled "Use +{ivy,helm}/project-search instead")
|
||||
(put 'projectile-ripgrep 'disabled "Use +{ivy,helm}/project-search instead")
|
||||
(put 'projectile-grep 'disabled "Use +{ivy,helm}/project-search instead")
|
||||
|
||||
;; Treat current directory in dired as a "file in a project" and track it
|
||||
(add-hook 'dired-before-readin-hook #'projectile-track-known-projects-find-file-hook)
|
||||
|
||||
;; Accidentally indexing big directories like $HOME or / will massively bloat
|
||||
;; projectile's cache (into the hundreds of MBs). This purges those entries
|
||||
;; when exiting Emacs to prevent slowdowns/freezing when cache files are
|
||||
;; loaded or written to.
|
||||
(add-hook! 'kill-emacs-hook
|
||||
(defun doom-cleanup-project-cache-h ()
|
||||
"Purge projectile cache entries that:
|
||||
|
||||
a) have too many files (see `doom-projectile-cache-limit'),
|
||||
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)
|
||||
projectile-enable-caching
|
||||
doom-interactive-p)
|
||||
(projectile-cleanup-known-projects)
|
||||
(cl-loop with blacklist = (mapcar #'file-truename doom-projectile-cache-blacklist)
|
||||
for proot in (hash-table-keys projectile-projects-cache)
|
||||
if (or (not (stringp proot))
|
||||
(>= (length (gethash proot projectile-projects-cache))
|
||||
doom-projectile-cache-limit)
|
||||
(member (substring proot 0 -1) blacklist)
|
||||
(and doom-projectile-cache-purge-non-projects
|
||||
(not (doom-project-p proot))))
|
||||
do (doom-log "Removed %S from projectile cache" proot)
|
||||
and do (remhash proot projectile-projects-cache)
|
||||
and do (remhash proot projectile-projects-cache-time)
|
||||
and do (remhash proot projectile-project-type-cache))
|
||||
(projectile-serialize-cache))))
|
||||
|
||||
;; It breaks projectile's project root resolution if HOME is a project (e.g.
|
||||
;; it's a git repo). In that case, we disable bottom-up root searching to
|
||||
;; prevent issues. This makes project resolution a little slower and less
|
||||
;; accurate in some cases.
|
||||
(let ((default-directory "~"))
|
||||
(when (cl-find-if #'projectile-file-exists-p
|
||||
projectile-project-root-files-bottom-up)
|
||||
(doom-log "HOME appears to be a project. Disabling bottom-up root search.")
|
||||
(setq projectile-project-root-files
|
||||
(append projectile-project-root-files-bottom-up
|
||||
projectile-project-root-files)
|
||||
projectile-project-root-files-bottom-up nil)))
|
||||
|
||||
;; Some utilities have issues with windows-style paths in MSYS, so emit
|
||||
;; unix-style paths instead.
|
||||
(when IS-WINDOWS
|
||||
(setenv "MSYS_NO_PATHCONV" "1"))
|
||||
|
||||
;; 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 (vcs)
|
||||
"Only use `projectile-generic-command' for indexing project files.
|
||||
And if it's a function, evaluate it."
|
||||
:override #'projectile-get-ext-command
|
||||
(if (functionp projectile-generic-command)
|
||||
(funcall projectile-generic-command vcs)
|
||||
projectile-generic-command))
|
||||
|
||||
;; `projectile-generic-command' doesn't typically support a function, but my
|
||||
;; `doom--only-use-generic-command-a' advice allows this. I do it this way so
|
||||
;; that projectile can adapt to remote systems (over TRAMP), rather then look
|
||||
;; for fd/ripgrep on the remote system simply because it exists on the host.
|
||||
;; It's faster too.
|
||||
(setq projectile-git-submodule-command nil
|
||||
projectile-indexing-method 'hybrid
|
||||
projectile-generic-command
|
||||
(lambda (_)
|
||||
;; 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.
|
||||
(cond
|
||||
((when-let
|
||||
(bin (if (ignore-errors (file-remote-p default-directory nil t))
|
||||
(cl-find-if (doom-rpartial #'executable-find t)
|
||||
(list "fdfind" "fd"))
|
||||
doom-projectile-fd-binary))
|
||||
(concat (format "%s . -0 -H --color=never --type file --type symlink --follow --exclude .git"
|
||||
bin)
|
||||
(if IS-WINDOWS " --path-separator=/"))))
|
||||
;; Otherwise, resort to ripgrep, which is also faster than find
|
||||
((executable-find "rg" t)
|
||||
(concat "rg -0 --files --follow --color=never --hidden -g!.git"
|
||||
(if IS-WINDOWS " --path-separator /")))
|
||||
("find . -type f -print0"))))
|
||||
|
||||
(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
|
||||
when using many of projectile's command, e.g. `projectile-compile-command',
|
||||
`projectile-run-project', `projectile-test-project', and
|
||||
`projectile-configure-project', for instance.
|
||||
|
||||
This suppresses the error so these commands will still run, but prompt you for
|
||||
the command instead."
|
||||
:around #'projectile-default-generic-command
|
||||
(ignore-errors (apply orig-fn args))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Project-based minor modes
|
||||
|
||||
(defvar doom-project-hook nil
|
||||
"Hook run when a project is enabled. The name of the project's mode and its
|
||||
state are passed in.")
|
||||
|
||||
(cl-defmacro def-project-mode! (name &key
|
||||
modes
|
||||
files
|
||||
when
|
||||
match
|
||||
add-hooks
|
||||
on-load
|
||||
on-enter
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
:modes MODES -- if buffers are derived from MODES (one or a list of symbols).
|
||||
|
||||
:files FILES -- if project contains FILES; takes a string or a form comprised
|
||||
of nested (and ...) and/or (or ...) forms. Each path is relative to the
|
||||
project root, however, if prefixed with a '.' or '..', it is relative to the
|
||||
current buffer.
|
||||
|
||||
:match REGEXP -- if file name matches REGEXP
|
||||
|
||||
:when PREDICATE -- if PREDICATE returns true (can be a form or the symbol of a
|
||||
function)
|
||||
|
||||
:add-hooks HOOKS -- HOOKS is a list of hooks to add this mode's hook.
|
||||
|
||||
:on-load FORM -- FORM to run the first time this project mode is enabled.
|
||||
|
||||
:on-enter FORM -- FORM is run each time the mode is activated.
|
||||
|
||||
:on-exit FORM -- FORM is run each time the mode is disabled.
|
||||
|
||||
Relevant: `doom-project-hook'."
|
||||
(declare (indent 1))
|
||||
(let ((init-var (intern (format "%s-init" name))))
|
||||
(macroexp-progn
|
||||
(append
|
||||
(when on-load
|
||||
`((defvar ,init-var nil)))
|
||||
`((define-minor-mode ,name
|
||||
"A project minor mode generated by `def-project-mode!'."
|
||||
:init-value nil
|
||||
:lighter ""
|
||||
:keymap (make-sparse-keymap)
|
||||
(if (not ,name)
|
||||
,on-exit
|
||||
(run-hook-with-args 'doom-project-hook ',name ,name)
|
||||
,(when on-load
|
||||
`(unless ,init-var
|
||||
,on-load
|
||||
(setq ,init-var t)))
|
||||
,on-enter))
|
||||
(dolist (hook ,add-hooks)
|
||||
(add-hook ',(intern (format "%s-hook" name)) hook)))
|
||||
(cond ((or files modes when)
|
||||
(cl-check-type files (or null list string))
|
||||
(let ((fn
|
||||
`(lambda ()
|
||||
(and (not (bound-and-true-p ,name))
|
||||
(and buffer-file-name (not (file-remote-p buffer-file-name nil t)))
|
||||
,(or (null match)
|
||||
`(if buffer-file-name (string-match-p ,match buffer-file-name)))
|
||||
,(or (null files)
|
||||
;; Wrap this in `eval' to prevent eager expansion
|
||||
;; of `project-file-exists-p!' from pulling in
|
||||
;; autoloaded files prematurely.
|
||||
`(eval
|
||||
'(project-file-exists-p!
|
||||
,(if (stringp (car files)) (cons 'and files) files))))
|
||||
,(or when t)
|
||||
(,name 1)))))
|
||||
(if modes
|
||||
`((dolist (mode ,modes)
|
||||
(let ((hook-name
|
||||
(intern (format "doom--enable-%s%s-h" ',name
|
||||
(if (eq mode t) "" (format "-in-%s" mode))))))
|
||||
(fset hook-name #',fn)
|
||||
(if (eq mode t)
|
||||
(add-to-list 'auto-minor-mode-magic-alist (cons hook-name #',name))
|
||||
(add-hook (intern (format "%s-hook" mode)) hook-name)))))
|
||||
`((add-hook 'change-major-mode-after-body-hook #',fn)))))
|
||||
(match
|
||||
`((add-to-list 'auto-minor-mode-alist (cons ,match #',name)))))))))
|
||||
|
||||
(provide 'core-projects)
|
||||
;;; core-projects.el ends here
|
|
@ -0,0 +1,741 @@
|
|||
;;; core-ui.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Variables
|
||||
|
||||
(defvar doom-init-theme-p nil
|
||||
"If non-nil, a theme has been loaded.")
|
||||
|
||||
(defvar doom-theme nil
|
||||
"A symbol representing the Emacs theme to load at startup.
|
||||
|
||||
Set to `default' to load no theme at all. This is changed by `load-theme'.")
|
||||
|
||||
(defvar doom-font nil
|
||||
"The default font to use.
|
||||
Must be a `font-spec', a font object, an XFT font string, or an XLFD string.
|
||||
|
||||
This affects the `default' and `fixed-pitch' faces.
|
||||
|
||||
Examples:
|
||||
(setq doom-font (font-spec :family \"Fira Mono\" :size 12))
|
||||
(setq doom-font \"Terminus (TTF):pixelsize=12:antialias=off\")")
|
||||
|
||||
(defvar doom-variable-pitch-font nil
|
||||
"The default font to use for variable-pitch text.
|
||||
Must be a `font-spec', a font object, an XFT font string, or an XLFD string. See
|
||||
`doom-font' for examples.
|
||||
|
||||
An omitted font size means to inherit `doom-font''s size.")
|
||||
|
||||
(defvar doom-serif-font nil
|
||||
"The default font to use for the `fixed-pitch-serif' face.
|
||||
Must be a `font-spec', a font object, an XFT font string, or an XLFD string. See
|
||||
`doom-font' for examples.
|
||||
|
||||
An omitted font size means to inherit `doom-font''s size.")
|
||||
|
||||
(defvar doom-unicode-font
|
||||
(if IS-MAC
|
||||
(font-spec :family "Apple Color Emoji")
|
||||
(font-spec :family "Symbola"))
|
||||
"Fallback font for Unicode glyphs.
|
||||
Must be a `font-spec', a font object, an XFT font string, or an XLFD string. See
|
||||
`doom-font' for examples.
|
||||
|
||||
The defaults on macOS and Linux are Apple Color Emoji and Symbola, respectively.
|
||||
|
||||
An omitted font size means 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
|
||||
|
||||
(defvar doom-init-ui-hook nil
|
||||
"List of hooks to run when the UI has been initialized.")
|
||||
|
||||
(defvar doom-load-theme-hook nil
|
||||
"Hook run after the theme is loaded with `load-theme' or reloaded with
|
||||
`doom/reload-theme'.")
|
||||
|
||||
(defvar doom-switch-buffer-hook nil
|
||||
"A list of hooks run after changing the current buffer.")
|
||||
|
||||
(defvar doom-switch-window-hook nil
|
||||
"A list of hooks run after changing the focused windows.")
|
||||
|
||||
(defvar doom-switch-frame-hook nil
|
||||
"A list of hooks run after changing the focused frame.")
|
||||
|
||||
(defvar doom-inhibit-switch-buffer-hooks nil
|
||||
"Letvar for inhibiting `doom-switch-buffer-hook'. Do not set this directly.")
|
||||
(defvar doom-inhibit-switch-window-hooks nil
|
||||
"Letvar for inhibiting `doom-switch-window-hook'. Do not set this directly.")
|
||||
(defvar doom-inhibit-switch-frame-hooks nil
|
||||
"Letvar for inhibiting `doom-switch-frame-hook'. Do not set this directly.")
|
||||
|
||||
(defvar doom--last-window nil)
|
||||
(defvar doom--last-frame nil)
|
||||
|
||||
(defun doom-run-switch-window-hooks-h ()
|
||||
(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 ((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)
|
||||
(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)
|
||||
(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'."
|
||||
(not (eq (current-buffer) (doom-fallback-buffer))))
|
||||
|
||||
(defun doom-highlight-non-default-indentation-h ()
|
||||
"Highlight whitespace at odds with `indent-tabs-mode'.
|
||||
That is, highlight tabs if `indent-tabs-mode' is `nil', and highlight spaces at
|
||||
the beginnings of lines if `indent-tabs-mode' is `t'. The purpose is to make
|
||||
incorrect indentation in the current buffer obvious to you.
|
||||
|
||||
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)
|
||||
(bound-and-true-p global-whitespace-mode)
|
||||
(null buffer-file-name))
|
||||
(require 'whitespace)
|
||||
(set (make-local-variable 'whitespace-style)
|
||||
(cl-union (if indent-tabs-mode
|
||||
'(indentation)
|
||||
'(tabs tab-mark))
|
||||
(when whitespace-mode
|
||||
(remq 'face whitespace-active-style))))
|
||||
(cl-pushnew 'face whitespace-style) ; must be first
|
||||
(whitespace-mode +1)))
|
||||
|
||||
|
||||
;;
|
||||
;;; General UX
|
||||
|
||||
;; 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
|
||||
visible-bell nil)
|
||||
|
||||
;; middle-click paste at point, not at click
|
||||
(setq mouse-yank-at-point t)
|
||||
|
||||
;; Larger column width for function name in profiler reports
|
||||
(after! profiler
|
||||
(setf (caar profiler-report-cpu-line-format) 80
|
||||
(caar profiler-report-memory-line-format) 80))
|
||||
|
||||
|
||||
;;
|
||||
;;; Scrolling
|
||||
|
||||
(setq hscroll-margin 2
|
||||
hscroll-step 1
|
||||
;; 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'
|
||||
;; for tall lines.
|
||||
auto-window-vscroll nil
|
||||
;; mouse
|
||||
mouse-wheel-scroll-amount '(5 ((shift) . 2))
|
||||
mouse-wheel-progressive-speed nil) ; don't accelerate scrolling
|
||||
|
||||
;; Remove hscroll-margin in shells, otherwise it causes jumpiness
|
||||
(setq-hook! '(eshell-mode-hook term-mode-hook) hscroll-margin 0)
|
||||
|
||||
|
||||
;;
|
||||
;;; Cursor
|
||||
|
||||
;; 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)
|
||||
|
||||
;; Don't stretch the cursor to fit wide characters, it is disorienting,
|
||||
;; especially for tabs.
|
||||
(setq x-stretch-cursor nil)
|
||||
|
||||
|
||||
;;
|
||||
;;; Buffers
|
||||
|
||||
;; Make `next-buffer', `other-buffer', etc. ignore unreal buffers.
|
||||
(push '(buffer-predicate . doom-buffer-frame-predicate) default-frame-alist)
|
||||
|
||||
(defadvice! doom--switch-to-fallback-buffer-maybe-a (&rest _)
|
||||
"Switch to `doom-fallback-buffer' if on last real buffer.
|
||||
|
||||
Advice for `kill-current-buffer'. If in a dedicated window, delete it. If there
|
||||
are no real buffers left OR if all remaining buffers are visible in other
|
||||
windows, switch to `doom-fallback-buffer'. Otherwise, delegate to original
|
||||
`kill-current-buffer'."
|
||||
:before-until #'kill-current-buffer
|
||||
(let ((buf (current-buffer)))
|
||||
(cond ((window-dedicated-p)
|
||||
(delete-window)
|
||||
t)
|
||||
((eq buf (doom-fallback-buffer))
|
||||
(message "Can't kill the fallback buffer.")
|
||||
t)
|
||||
((doom-real-buffer-p buf)
|
||||
(let ((visible-p (delq (selected-window) (get-buffer-window-list buf nil t)))
|
||||
(doom-inhibit-switch-buffer-hooks t)
|
||||
(inhibit-redisplay t)
|
||||
buffer-list-update-hook)
|
||||
(unless visible-p
|
||||
(when (and (buffer-modified-p buf)
|
||||
(not (y-or-n-p
|
||||
(format "Buffer %s is modified; kill anyway?"
|
||||
buf))))
|
||||
(user-error "Aborted")))
|
||||
(when (or ;; if there aren't more real buffers than visible buffers,
|
||||
;; then there are no real, non-visible buffers left.
|
||||
(not (cl-set-difference (doom-real-buffer-list)
|
||||
(doom-visible-buffers)))
|
||||
;; if we end up back where we start (or previous-buffer
|
||||
;; returns nil), we have nowhere left to go
|
||||
(memq (switch-to-prev-buffer nil t) (list buf 'nil)))
|
||||
(switch-to-buffer (doom-fallback-buffer)))
|
||||
(unless visible-p
|
||||
(with-current-buffer buf
|
||||
(restore-buffer-modified-p nil))
|
||||
(kill-buffer buf))
|
||||
(run-hooks 'buffer-list-update-hook)
|
||||
t)))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Fringes
|
||||
|
||||
;; Reduce the clutter in the fringes; we'd like to reserve that space for more
|
||||
;; useful information, like git-gutter and flycheck.
|
||||
(setq indicate-buffer-boundaries nil
|
||||
indicate-empty-lines nil)
|
||||
|
||||
|
||||
;;
|
||||
;;; Windows/frames
|
||||
|
||||
;; A simple frame title
|
||||
(setq frame-title-format '("%b – Doom Emacs")
|
||||
icon-title-format frame-title-format)
|
||||
|
||||
;; Don't resize the frames in steps; it looks weird, especially in tiling window
|
||||
;; managers, where it can leave unseemly gaps.
|
||||
(setq frame-resize-pixelwise t)
|
||||
|
||||
;; But do not resize windows pixelwise, this can cause crashes in some cases
|
||||
;; where we resize windows too quickly.
|
||||
(setq window-resize-pixelwise nil)
|
||||
|
||||
;; Disable tool, menu, and scrollbars. Doom is designed to be keyboard-centric,
|
||||
;; so these are just clutter (the scrollbar also impacts performance). Whats
|
||||
;; more, the menu bar exposes functionality that Doom doesn't endorse.
|
||||
(push '(menu-bar-lines . 0) default-frame-alist)
|
||||
(push '(tool-bar-lines . 0) default-frame-alist)
|
||||
(push '(vertical-scroll-bars) default-frame-alist)
|
||||
|
||||
;; These are disabled directly through their frame parameters to avoid the extra
|
||||
;; work their minor modes do, but their variables must be unset too, 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)
|
||||
|
||||
;; The native border "consumes" a pixel of the fringe on righter-most splits,
|
||||
;; `window-divider' does not. Available since Emacs 25.1.
|
||||
(setq window-divider-default-places t
|
||||
window-divider-default-bottom-width 1
|
||||
window-divider-default-right-width 1)
|
||||
(add-hook 'doom-init-ui-hook #'window-divider-mode)
|
||||
|
||||
;; 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,
|
||||
;; 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. Screens are usually wide.
|
||||
(setq split-width-threshold 160
|
||||
split-height-threshold nil)
|
||||
|
||||
|
||||
;;
|
||||
;;; Minibuffer
|
||||
|
||||
;; Allow for minibuffer-ception. Sometimes we need another minibuffer command
|
||||
;; while we're in the minibuffer.
|
||||
(setq enable-recursive-minibuffers t)
|
||||
|
||||
;; 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
|
||||
;; doesn't look too great with direnv, however...
|
||||
(setq resize-mini-windows 'grow-only)
|
||||
|
||||
;; Typing yes/no is obnoxious when y/n will do
|
||||
(fset #'yes-or-no-p #'y-or-n-p)
|
||||
|
||||
;; Try really hard to keep the cursor from getting stuck in the read-only prompt
|
||||
;; portion of the minibuffer.
|
||||
(setq minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt))
|
||||
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
|
||||
|
||||
|
||||
;;
|
||||
;;; Built-in packages
|
||||
|
||||
;;;###package ansi-color
|
||||
(setq ansi-color-for-comint-mode t)
|
||||
|
||||
|
||||
(after! comint
|
||||
(setq comint-prompt-read-only t))
|
||||
|
||||
|
||||
(after! compile
|
||||
(setq compilation-always-kill t ; kill compilation process before starting another
|
||||
compilation-ask-about-save nil ; save all buffers on `compile'
|
||||
compilation-scroll-output 'first-error)
|
||||
;; Handle ansi codes in compilation buffer
|
||||
(add-hook 'compilation-filter-hook #'doom-apply-ansi-color-to-compilation-buffer-h))
|
||||
|
||||
|
||||
(after! ediff
|
||||
(setq ediff-diff-options "-w" ; turn off whitespace checking
|
||||
ediff-split-window-function #'split-window-horizontally
|
||||
ediff-window-setup-function #'ediff-setup-windows-plain)
|
||||
|
||||
(defvar doom--ediff-saved-wconf nil)
|
||||
;; Restore window config after quitting ediff
|
||||
(add-hook! 'ediff-before-setup-hook
|
||||
(defun doom-ediff-save-wconf-h ()
|
||||
(setq doom--ediff-saved-wconf (current-window-configuration))))
|
||||
(add-hook! '(ediff-quit-hook ediff-suspend-hook) :append
|
||||
(defun doom-ediff-restore-wconf-h ()
|
||||
(when (window-configuration-p doom--ediff-saved-wconf)
|
||||
(set-window-configuration doom--ediff-saved-wconf)))))
|
||||
|
||||
|
||||
(use-package! hl-line
|
||||
;; Highlights the current line
|
||||
:hook (doom-first-buffer . global-hl-line-mode)
|
||||
:init
|
||||
(defvar global-hl-line-modes '(prog-mode text-mode conf-mode special-mode)
|
||||
"What modes to enable `hl-line-mode' in.")
|
||||
:config
|
||||
;; HACK I reimplement `global-hl-line-mode' so we can white/blacklist modes in
|
||||
;; `global-hl-line-modes' _and_ so we can use `global-hl-line-mode',
|
||||
;; which users expect to control hl-line in Emacs.
|
||||
(define-globalized-minor-mode global-hl-line-mode hl-line-mode
|
||||
(lambda ()
|
||||
(and (not hl-line-mode)
|
||||
(cond ((null global-hl-line-modes) nil)
|
||||
((eq global-hl-line-modes t))
|
||||
((eq (car global-hl-line-modes) 'not)
|
||||
(not (derived-mode-p global-hl-line-modes)))
|
||||
((apply #'derived-mode-p global-hl-line-modes)))
|
||||
(hl-line-mode +1))))
|
||||
|
||||
;; 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)
|
||||
|
||||
;; 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! 'hl-line-mode-hook
|
||||
(defun doom-truly-disable-hl-line-h ()
|
||||
(unless hl-line-mode
|
||||
(setq-local 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
|
||||
(hl-line-mode -1)
|
||||
(setq-local doom--hl-line-mode t))))
|
||||
|
||||
(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
|
||||
:preface (defvar winner-dont-bind-my-keys t) ; I'll bind keys myself
|
||||
:hook (doom-first-buffer . winner-mode)
|
||||
:config
|
||||
(appendq! winner-boring-buffers
|
||||
'("*Compile-Log*" "*inferior-lisp*" "*Fuzzy Completions*"
|
||||
"*Apropos*" "*Help*" "*cvs*" "*Buffer List*" "*Ibuffer*"
|
||||
"*esh command on file*")))
|
||||
|
||||
|
||||
(use-package! paren
|
||||
;; highlight matching delimiters
|
||||
: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))
|
||||
|
||||
|
||||
;;;###package whitespace
|
||||
(setq whitespace-line-column nil
|
||||
whitespace-style
|
||||
'(face indentation tabs tab-mark spaces space-mark newline newline-mark
|
||||
trailing lines-tail)
|
||||
whitespace-display-mappings
|
||||
'((tab-mark ?\t [?› ?\t])
|
||||
(newline-mark ?\n [?¬ ?\n])
|
||||
(space-mark ?\ [?·] [?.])))
|
||||
|
||||
|
||||
;;
|
||||
;;; Third party packages
|
||||
|
||||
(use-package! all-the-icons
|
||||
:commands (all-the-icons-octicon
|
||||
all-the-icons-faicon
|
||||
all-the-icons-fileicon
|
||||
all-the-icons-wicon
|
||||
all-the-icons-material
|
||||
all-the-icons-alltheicon)
|
||||
: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)
|
||||
#'hide-mode-line-mode)
|
||||
|
||||
;; Many major modes do no highlighting of number literals, so we do it for them
|
||||
(use-package! highlight-numbers
|
||||
:hook ((prog-mode conf-mode) . highlight-numbers-mode)
|
||||
:config (setq highlight-numbers-generic-regexp "\\_<[[:digit:]]+\\(?:\\.[0-9]*\\)?\\_>"))
|
||||
|
||||
;;;###package image
|
||||
(setq image-animate-loop t)
|
||||
|
||||
;;;###package rainbow-delimiters
|
||||
;; Helps us distinguish stacked delimiter pairs, especially in parentheses-drunk
|
||||
;; languages like Lisp.
|
||||
(setq rainbow-delimiters-max-face-count 3)
|
||||
|
||||
|
||||
;;
|
||||
;;; Line numbers
|
||||
|
||||
;; Explicitly define a width to reduce computation
|
||||
(setq-default display-line-numbers-width 3)
|
||||
|
||||
;; 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. 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)
|
||||
|
||||
;; Fix #2742: cursor is off by 4 characters in `artist-mode'
|
||||
;; REVIEW Reported upstream https://debbugs.gnu.org/cgi/bugreport.cgi?bug=43811
|
||||
;; DEPRECATED Fixed in Emacs 28; remove when we drop 27 support
|
||||
(unless EMACS28+
|
||||
(add-hook 'artist-mode-hook #'doom-disable-line-numbers-h))
|
||||
|
||||
|
||||
;;
|
||||
;;; Theme & font
|
||||
|
||||
;; User themes should live in ~/.doom.d/themes, not ~/.emacs.d
|
||||
(setq custom-theme-directory (concat doom-private-dir "themes/"))
|
||||
|
||||
;; Always prioritize the user's themes above the built-in/packaged ones.
|
||||
(setq custom-theme-load-path
|
||||
(cons 'custom-theme-directory
|
||||
(remq 'custom-theme-directory custom-theme-load-path)))
|
||||
|
||||
;; 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
|
||||
;; 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 (fboundp 'set-fontset-font)
|
||||
(dolist (font (cons doom-unicode-font doom-unicode-extra-fonts))
|
||||
(set-fontset-font t 'unicode font nil 'prepend)))
|
||||
(run-hooks 'after-setting-font-hook))
|
||||
((debug error)
|
||||
(if (string-prefix-p "Font not available: " (error-message-string e))
|
||||
(lwarn 'doom-ui :warning
|
||||
"Could not find the '%s' font on your system, falling back to system font"
|
||||
(font-get (caddr e) :family))
|
||||
(signal 'doom-error e)))))
|
||||
|
||||
(defun doom-init-theme-h (&optional frame)
|
||||
"Load the theme specified by `doom-theme' in FRAME."
|
||||
(when (and doom-theme
|
||||
(not (eq doom-theme 'default))
|
||||
(not (memq doom-theme custom-enabled-themes)))
|
||||
(with-selected-frame (or frame (selected-frame))
|
||||
(let ((doom--prefer-theme-elc t)) ; DEPRECATED in Emacs 27
|
||||
(load-theme doom-theme t)))))
|
||||
|
||||
(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.
|
||||
|
||||
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
|
||||
;; 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 (remq 'use-package custom-enabled-themes)))
|
||||
(run-hooks 'doom-load-theme-hook))
|
||||
result)))
|
||||
|
||||
(eval-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 (remq 'use-package 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))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Bootstrap
|
||||
|
||||
(defun doom-init-ui-h ()
|
||||
"Initialize Doom's user interface by applying all its advice and hooks."
|
||||
(run-hook-wrapped 'doom-init-ui-hook #'doom-try-run-hook)
|
||||
|
||||
(add-hook 'kill-buffer-query-functions #'doom-protect-fallback-buffer-h)
|
||||
(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))
|
||||
(advice-add fn :around #'doom-run-switch-to-next-prev-buffer-hooks-a))
|
||||
(dolist (fn '(switch-to-buffer display-buffer))
|
||||
(advice-add fn :around #'doom-run-switch-buffer-hooks-a)))
|
||||
|
||||
;; Apply `doom-font' et co
|
||||
(add-hook 'doom-after-init-modules-hook #'doom-init-fonts-h)
|
||||
(add-hook 'doom-load-theme-hook #'doom-init-extra-fonts-h)
|
||||
|
||||
;; Apply `doom-theme'
|
||||
(add-hook (if (daemonp)
|
||||
'after-make-frame-functions
|
||||
'doom-init-ui-hook)
|
||||
#'doom-init-theme-h)
|
||||
|
||||
(add-hook 'window-setup-hook #'doom-init-ui-h)
|
||||
|
||||
|
||||
;;
|
||||
;;; Fixes/hacks
|
||||
|
||||
;; Doom doesn't support `customize' and it never will. It's a clumsy interface
|
||||
;; 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, 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 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))
|
||||
|
||||
;; Don't display messages in the minibuffer when using the minibuffer
|
||||
(defmacro doom-silence-motion-key (command key)
|
||||
(let ((key-command (intern (format "doom/silent-%s" command))))
|
||||
`(progn
|
||||
(defun ,key-command ()
|
||||
(interactive)
|
||||
(ignore-errors (call-interactively ',command)))
|
||||
(define-key minibuffer-local-map (kbd ,key) #',key-command))))
|
||||
(doom-silence-motion-key backward-delete-char "<backspace>")
|
||||
(doom-silence-motion-key delete-char "<delete>")
|
||||
|
||||
(provide 'core-ui)
|
||||
;;; core-ui.el ends here
|
|
@ -0,0 +1,635 @@
|
|||
;;; core.el --- the heart of the beast -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Initialize internal state
|
||||
|
||||
(defconst doom-version "2.0.9"
|
||||
"Current version of Doom Emacs.")
|
||||
|
||||
(defvar doom-init-p nil
|
||||
"Non-nil if Doom has been initialized.")
|
||||
|
||||
(defvar doom-init-time nil
|
||||
"The time it took, in seconds, for Doom Emacs to initialize.")
|
||||
|
||||
(defvar doom-debug-p (or (getenv-internal "DEBUG") init-file-debug)
|
||||
"If non-nil, Doom will log more.
|
||||
|
||||
Use `doom-debug-mode' to toggle it. The --debug-init flag and setting the DEBUG
|
||||
envvar will enable this at startup.")
|
||||
|
||||
(defconst doom-interactive-p (not noninteractive)
|
||||
"If non-nil, Emacs is in interactive mode.")
|
||||
|
||||
(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-internal "HOME")))
|
||||
(setenv "HOME" (getenv "USERPROFILE"))
|
||||
(setq abbreviated-home-dir nil))
|
||||
|
||||
;; 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
|
||||
;; The clipboard's on Windows could be in a wider (or thinner) encoding than
|
||||
;; utf-8 (likely UTF-16), so let Emacs/the OS decide what encoding to use there.
|
||||
(unless IS-WINDOWS
|
||||
(setq selection-coding-system 'utf-8)) ; with sugar on top
|
||||
|
||||
;;; 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 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)
|
||||
|
||||
|
||||
;;
|
||||
;;; Directory variables
|
||||
|
||||
(defconst doom-emacs-dir user-emacs-directory
|
||||
"The path to the currently loaded .emacs.d directory. Must end with a slash.")
|
||||
|
||||
(defconst doom-core-dir (concat doom-emacs-dir "core/")
|
||||
"The root directory of Doom's core files. Must end with a slash.")
|
||||
|
||||
(defconst doom-modules-dir (concat doom-emacs-dir "modules/")
|
||||
"The root directory for Doom's modules. Must end with a slash.")
|
||||
|
||||
(defconst doom-local-dir
|
||||
(let ((localdir (getenv-internal "DOOMLOCALDIR")))
|
||||
(if localdir
|
||||
(expand-file-name (file-name-as-directory localdir))
|
||||
(concat doom-emacs-dir ".local/")))
|
||||
"Root directory for local storage.
|
||||
|
||||
Use this as a storage location for this system's installation of Doom Emacs.
|
||||
|
||||
These files should not be shared across systems. By default, it is used by
|
||||
`doom-etc-dir' and `doom-cache-dir'. Must end with a slash.")
|
||||
|
||||
(defconst doom-etc-dir (concat doom-local-dir "etc/")
|
||||
"Directory for non-volatile local storage.
|
||||
|
||||
Use this for files that don't change much, like server binaries, external
|
||||
dependencies or long-term shared data. Must end with a slash.")
|
||||
|
||||
(defconst doom-cache-dir (concat doom-local-dir "cache/")
|
||||
"Directory for volatile local storage.
|
||||
|
||||
Use this for files that change often, like cache files. Must end with a slash.")
|
||||
|
||||
(defconst doom-docs-dir (concat doom-emacs-dir "docs/")
|
||||
"Where Doom's documentation files are stored. Must end with a slash.")
|
||||
|
||||
(defconst doom-private-dir
|
||||
(let ((doomdir (getenv-internal "DOOMDIR")))
|
||||
(if doomdir
|
||||
(expand-file-name (file-name-as-directory doomdir))
|
||||
(or (let ((xdgdir
|
||||
(expand-file-name "doom/"
|
||||
(or (getenv-internal "XDG_CONFIG_HOME")
|
||||
"~/.config"))))
|
||||
(if (file-directory-p xdgdir) xdgdir))
|
||||
"~/.doom.d/")))
|
||||
"Where your private configuration is placed.
|
||||
|
||||
Defaults to ~/.config/doom, ~/.doom.d or the value of the DOOMDIR envvar;
|
||||
whichever is found first. Must end in a slash.")
|
||||
|
||||
(defconst doom-autoloads-file
|
||||
(concat doom-local-dir "autoloads." emacs-version ".el")
|
||||
"Where `doom-reload-core-autoloads' stores its core autoloads.
|
||||
|
||||
This file is responsible for informing Emacs where to find all of Doom's
|
||||
autoloaded core functions (in core/autoload/*.el).")
|
||||
|
||||
(defconst doom-env-file (concat doom-local-dir "env")
|
||||
"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
|
||||
\(easily) be launched from the correct shell session (particularly for MacOS
|
||||
users).")
|
||||
|
||||
|
||||
;;
|
||||
;;; 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.")
|
||||
|
||||
(defvar doom-after-reload-hook nil
|
||||
"A list of hooks to run before `doom/reload' has reloaded Doom.")
|
||||
|
||||
(defvar doom-before-reload-hook nil
|
||||
"A list of hooks to run after `doom/reload' has reloaded Doom.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Native Compilation support (http://akrl.sdf.org/gccemacs.html)
|
||||
|
||||
;; Prevent unwanted runtime builds in gccemacs (native-comp); packages are
|
||||
;; compiled ahead-of-time when they are installed and site files are compiled
|
||||
;; when gccemacs is installed.
|
||||
(setq comp-deferred-compilation nil)
|
||||
|
||||
;; Don't store eln files in ~/.emacs.d/eln-cache (they are likely to be purged
|
||||
;; when upgrading Doom).
|
||||
(when (boundp 'comp-eln-load-path)
|
||||
(add-to-list 'comp-eln-load-path (concat doom-cache-dir "eln/")))
|
||||
|
||||
(with-eval-after-load 'comp
|
||||
;; HACK Disable native-compilation for some troublesome packages
|
||||
(mapc (doom-partial #'add-to-list 'comp-deferred-compilation-deny-list)
|
||||
(let ((local-dir-re (concat "\\`" (regexp-quote doom-local-dir))))
|
||||
(list (concat "\\`" (regexp-quote doom-autoloads-file) "\\'")
|
||||
(concat local-dir-re ".*/evil-collection-vterm\\.el\\'")
|
||||
(concat local-dir-re ".*/with-editor\\.el\\'")
|
||||
;; https://github.com/nnicandro/emacs-jupyter/issues/297
|
||||
(concat local-dir-re ".*/jupyter-channel\\.el\\'"))))
|
||||
;; Default to using all cores, rather than half of them, since we compile
|
||||
;; things ahead-of-time in a non-interactive session.
|
||||
(defadvice! doom--comp-use-all-cores-a ()
|
||||
:override #'comp-effective-async-max-jobs
|
||||
(if (zerop comp-async-jobs-number)
|
||||
(setq comp-num-cpus (doom-num-cpus))
|
||||
comp-async-jobs-number)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Core libraries
|
||||
|
||||
;; Ensure Doom's core libraries are visible for loading
|
||||
(add-to-list 'load-path doom-core-dir)
|
||||
|
||||
;; Just the... bear necessities~
|
||||
(require 'subr-x)
|
||||
(require 'cl-lib)
|
||||
(require 'core-lib)
|
||||
|
||||
|
||||
;;
|
||||
;;; A quieter startup
|
||||
|
||||
;; 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)
|
||||
|
||||
;; Reduce debug output, well, unless we've asked for it.
|
||||
(setq debug-on-error doom-debug-p
|
||||
jka-compr-verbose doom-debug-p)
|
||||
|
||||
;; 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))
|
||||
|
||||
;; Reduce *Message* noise at startup. An empty scratch buffer (or the dashboard)
|
||||
;; is more than enough.
|
||||
(setq inhibit-startup-message t
|
||||
inhibit-startup-echo-area-message user-login-name
|
||||
inhibit-default-init t
|
||||
;; Shave seconds off startup time by starting the scratch buffer in
|
||||
;; `fundamental-mode', rather than, say, `org-mode' or `text-mode', which
|
||||
;; pull in a ton of packages.
|
||||
initial-major-mode 'fundamental-mode
|
||||
initial-scratch-message nil)
|
||||
|
||||
|
||||
;;
|
||||
;;; Don't litter `doom-emacs-dir'
|
||||
|
||||
;; We avoid `no-littering' because it's a mote too opinionated for our needs.
|
||||
(setq async-byte-compile-log-file (concat doom-etc-dir "async-bytecomp.log")
|
||||
custom-file (concat doom-private-dir "custom.el")
|
||||
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")
|
||||
shared-game-score-directory (concat doom-etc-dir "shared-game-score/"))
|
||||
|
||||
(defadvice! doom--write-to-etc-dir-a (orig-fn &rest args)
|
||||
"Resolve Emacs storage directory to `doom-etc-dir', to keep local files from
|
||||
polluting `doom-emacs-dir'."
|
||||
:around #'locate-user-emacs-file
|
||||
(let ((user-emacs-directory doom-etc-dir))
|
||||
(apply orig-fn args)))
|
||||
|
||||
(defadvice! doom--write-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
|
||||
|
||||
;; 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)
|
||||
|
||||
;; 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)
|
||||
|
||||
;; Disabling the BPA makes redisplay faster, but might produce incorrect display
|
||||
;; reordering of bidirectional text with embedded parentheses and other bracket
|
||||
;; characters whose 'paired-bracket' Unicode property is non-nil.
|
||||
(setq bidi-inhibit-bpa t) ; Emacs 27 only
|
||||
|
||||
;; Reduce rendering/line scan work for Emacs by not rendering cursors or regions
|
||||
;; in non-focused windows.
|
||||
(setq-default cursor-in-non-selected-windows nil)
|
||||
(setq highlight-nonselected-windows nil)
|
||||
|
||||
;; More performant rapid scrolling over unfontified regions. May cause brief
|
||||
;; spells of inaccurate syntax highlighting right after scrolling, which should
|
||||
;; quickly self-correct.
|
||||
(setq fast-but-imprecise-scrolling t)
|
||||
|
||||
;; Don't ping things that look like domain names.
|
||||
(setq ffap-machine-p-known 'reject)
|
||||
|
||||
;; Resizing the Emacs frame can be a terribly expensive part of changing the
|
||||
;; font. By inhibiting this, we halve startup times, particularly when we use
|
||||
;; fonts that are larger than the system default (which would resize the frame).
|
||||
(setq frame-inhibit-implied-resize t)
|
||||
|
||||
;; Adopt a sneaky garbage collection strategy of waiting until idle time to
|
||||
;; collect; staving off the collector while the user is working.
|
||||
(setq gcmh-idle-delay 5
|
||||
gcmh-high-cons-threshold (* 16 1024 1024) ; 16mb
|
||||
gcmh-verbose doom-debug-p)
|
||||
|
||||
;; Emacs "updates" its ui more often than it needs to, so we slow it down
|
||||
;; slightly from 0.5s:
|
||||
(setq idle-update-delay 1.0)
|
||||
|
||||
;; Font compacting can be terribly expensive, especially for rendering icon
|
||||
;; fonts on Windows. Whether disabling it has a notable affect on Linux and Mac
|
||||
;; hasn't been determined, but we inhibit it there anyway. This increases memory
|
||||
;; usage, however!
|
||||
(setq inhibit-compacting-font-caches t)
|
||||
|
||||
;; Introduced in Emacs HEAD (b2f8c9f), this inhibits fontification while
|
||||
;; receiving input, which should help with performance while scrolling.
|
||||
(setq redisplay-skip-fontification-on-input t)
|
||||
|
||||
;; Performance on Windows is considerably worse than elsewhere. We'll need
|
||||
;; everything we can get.
|
||||
(when IS-WINDOWS
|
||||
(setq w32-get-true-file-attributes nil ; decrease file IO workload
|
||||
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))
|
||||
|
||||
;; HACK `tty-run-terminal-initialization' is *tremendously* slow for some
|
||||
;; reason; inexplicably doubling startup time for terminal Emacs. Keeping
|
||||
;; it disabled will have nasty side-effects, so we simply delay it until
|
||||
;; later in the startup process and, for some reason, it runs much faster
|
||||
;; when it does.
|
||||
(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))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Security
|
||||
|
||||
;; 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-internal "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
|
||||
;; 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"))
|
||||
|
||||
;; 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 (concat doom-etc-dir "authinfo.gpg")
|
||||
"~/.authinfo.gpg"))
|
||||
|
||||
|
||||
;;
|
||||
;;; MODE-local-vars-hook
|
||||
|
||||
;; File+dir local variables are initialized after the major mode and its hooks
|
||||
;; have run. If you want hook functions to be aware of these customizations, add
|
||||
;; them to MODE-local-vars-hook instead.
|
||||
(defvar doom-inhibit-local-var-hooks nil)
|
||||
|
||||
(defun doom-run-local-var-hooks-h ()
|
||||
"Run MODE-local-vars-hook after local variables are initialized."
|
||||
(unless doom-inhibit-local-var-hooks
|
||||
(setq-local doom-inhibit-local-var-hooks t)
|
||||
(run-hook-wrapped (intern-soft (format "%s-local-vars-hook" major-mode))
|
||||
#'doom-try-run-hook)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Incremental lazy-loading
|
||||
|
||||
(defvar doom-incremental-packages '(t)
|
||||
"A list of packages to load incrementally after startup. Any large packages
|
||||
here may cause noticable pauses, so it's recommended you break them up into
|
||||
sub-packages. For example, `org' is comprised of many packages, and can be
|
||||
broken up into:
|
||||
|
||||
(doom-load-packages-incrementally
|
||||
'(calendar find-func format-spec org-macs org-compat
|
||||
org-faces org-entities org-list org-pcomplete org-src
|
||||
org-footnote org-macro ob org org-clock org-agenda
|
||||
org-capture))
|
||||
|
||||
This is already done by the lang/org module, however.
|
||||
|
||||
If you want to disable incremental loading altogether, either remove
|
||||
`doom-load-packages-incrementally-h' from `emacs-startup-hook' or set
|
||||
`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.0
|
||||
"How long (in idle seconds) until incremental loading starts.
|
||||
|
||||
Set this to nil to disable incremental loading.")
|
||||
|
||||
(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)
|
||||
(appendq! doom-incremental-packages packages)
|
||||
(while packages
|
||||
(let ((req (pop packages)))
|
||||
(unless (featurep req)
|
||||
(doom-log "Incrementally loading %s" req)
|
||||
(condition-case e
|
||||
(or (while-no-input
|
||||
;; If `default-directory' is a directory that doesn't exist
|
||||
;; or is unreadable, Emacs throws up file-missing errors, so
|
||||
;; we set it to a directory we know exists and is readable.
|
||||
(let ((default-directory doom-emacs-dir)
|
||||
(gc-cons-threshold most-positive-fixnum)
|
||||
file-name-handler-alist)
|
||||
(require req nil t))
|
||||
t)
|
||||
(push req packages))
|
||||
((error debug)
|
||||
(message "Failed to load %S package incrementally, because: %s"
|
||||
req e)))
|
||||
(if (not packages)
|
||||
(doom-log "Finished incremental loading")
|
||||
(run-with-idle-timer doom-incremental-idle-timer
|
||||
nil #'doom-load-packages-incrementally
|
||||
packages t)
|
||||
(setq packages nil)))))))
|
||||
|
||||
(defun doom-load-packages-incrementally-h ()
|
||||
"Begin incrementally loading packages in `doom-incremental-packages'.
|
||||
|
||||
If this is a daemon session, load them all immediately instead."
|
||||
(if doom-incremental-load-immediately
|
||||
(mapc #'require (cdr doom-incremental-packages))
|
||||
(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))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Bootstrap helpers
|
||||
|
||||
(defun doom-display-benchmark-h (&optional return-p)
|
||||
"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)
|
||||
"Doom loaded %d packages across %d modules in %.03fs"
|
||||
(- (length load-path) (length doom--initial-load-path))
|
||||
(if doom-modules (hash-table-count doom-modules) 0)
|
||||
(or doom-init-time
|
||||
(setq doom-init-time
|
||||
(float-time (time-subtract (current-time) before-init-time))))))
|
||||
|
||||
(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)))
|
||||
|
||||
(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-run-hook-on (hook-var triggers)
|
||||
"Configure HOOK-VAR to be invoked exactly once after init whenever any of the
|
||||
TRIGGERS are invoked. Once HOOK-VAR gets triggered, it resets to nil.
|
||||
|
||||
HOOK-VAR is a quoted hook.
|
||||
|
||||
TRIGGERS is a list of quoted hooks and/or sharp-quoted functions."
|
||||
(let ((fn (intern (format "%s-h" hook-var))))
|
||||
(fset
|
||||
fn (lambda (&rest _)
|
||||
(when after-init-time
|
||||
(run-hook-wrapped hook-var #'doom-try-run-hook)
|
||||
(set hook-var nil))))
|
||||
(put hook-var 'permanent-local t)
|
||||
(dolist (on triggers)
|
||||
(if (functionp on)
|
||||
(advice-add on :before fn)
|
||||
(add-hook on fn)))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Bootstrapper
|
||||
|
||||
(defvar doom--initial-exec-path exec-path)
|
||||
(defvar doom--initial-load-path load-path)
|
||||
(defvar doom--initial-process-environment process-environment)
|
||||
|
||||
(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 everything Doom needs to run is set up;
|
||||
essential directories exist, core packages are installed, `doom-autoloads-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:
|
||||
|
||||
~/.emacs.d/init.el
|
||||
~/.emacs.d/core/core.el
|
||||
~/.doom.d/init.el
|
||||
Module init.el files
|
||||
`doom-before-init-modules-hook'
|
||||
Module config.el files
|
||||
~/.doom.d/config.el
|
||||
`doom-init-modules-hook'
|
||||
`doom-after-init-modules-hook' (alias for `after-init-hook')
|
||||
`emacs-startup-hook'
|
||||
`doom-init-ui-hook'
|
||||
`window-setup-hook'
|
||||
|
||||
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)."
|
||||
(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. 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)
|
||||
|
||||
;; Doom caches a lot of information in `doom-autoloads-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-autoloads-file)
|
||||
nil 'nomessage)
|
||||
(file-missing
|
||||
;; If the autoloads file fails to load then the user forgot to sync, or
|
||||
;; aborted a doom command midway!
|
||||
(if (locate-file doom-autoloads-file load-path)
|
||||
;; Something inside the autoloads file is triggering this error;
|
||||
;; forward it to the caller!
|
||||
(signal 'doom-autoload-error e)
|
||||
(signal 'doom-error
|
||||
(list "Doom is in an incomplete state"
|
||||
"run '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.
|
||||
(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)
|
||||
|
||||
;; There's a chance the user will later use package.el or straight in this
|
||||
;; interactive session. If they do, make sure they're properly initialized
|
||||
;; when they do.
|
||||
(autoload 'doom-initialize-packages "core-packages")
|
||||
(eval-after-load 'package '(require 'core-packages))
|
||||
(eval-after-load 'straight '(doom-initialize-packages))
|
||||
|
||||
;; Bootstrap our GC manager
|
||||
(add-hook 'doom-first-buffer-hook #'gcmh-mode)
|
||||
|
||||
;; Bootstrap the interactive session
|
||||
(add-hook 'after-change-major-mode-hook #'doom-run-local-var-hooks-h)
|
||||
(add-hook 'emacs-startup-hook #'doom-load-packages-incrementally-h)
|
||||
(add-hook 'window-setup-hook #'doom-display-benchmark-h)
|
||||
(doom-run-hook-on 'doom-first-buffer-hook '(after-find-file doom-switch-buffer-hook))
|
||||
(doom-run-hook-on 'doom-first-file-hook '(after-find-file dired-initial-position-hook))
|
||||
(doom-run-hook-on 'doom-first-input-hook '(pre-command-hook))
|
||||
(if doom-debug-p (doom-debug-mode +1))
|
||||
|
||||
;; Load core/core-*.el, the user's private init.el, then their config.el
|
||||
(doom-initialize-modules force-p))
|
||||
|
||||
doom-init-p)
|
||||
|
||||
(provide 'core)
|
||||
;;; core.el ends here
|
|
@ -0,0 +1,58 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/packages.el
|
||||
|
||||
;; core.el
|
||||
(package! auto-minor-mode :pin "17cfa1b54800fdef2975c0c0531dad34846a5065")
|
||||
(package! gcmh :pin "0089f9c3a6d4e9a310d0791cf6fa8f35642ecfd9")
|
||||
(package! explain-pause-mode
|
||||
:recipe (:host github
|
||||
:repo "lastquestion/explain-pause-mode")
|
||||
:pin "2356c8c3639cbeeb9751744dbe737267849b4b51")
|
||||
|
||||
;; 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"))
|
||||
:pin "a32c97cb427b7f14dfd066f36a58d1740e20ed69")
|
||||
|
||||
;; core-modules.el
|
||||
(package! use-package
|
||||
:type 'core
|
||||
:pin "365c73d2618dd0040a32c2601c5456ab5495b812")
|
||||
|
||||
;; core-ui.el
|
||||
(package! all-the-icons :pin "9aa16ae198073fe839a0abfa9a7d3a9dc85ef5f9")
|
||||
(package! hide-mode-line :pin "88888825b5b27b300683e662fa3be88d954b1cea")
|
||||
(package! highlight-numbers :pin "8b4744c7f46c72b1d3d599d4fb75ef8183dee307")
|
||||
(package! rainbow-delimiters :pin "f43d48a24602be3ec899345a3326ed0247b960c6")
|
||||
(package! restart-emacs :pin "1607da2bc657fe05ae01f7fdf26f716eafead02c")
|
||||
|
||||
;; core-editor.el
|
||||
(package! better-jumper :pin "5ef53fcee4e74f397c8d275679e5596b52582b57")
|
||||
(package! dtrt-indent :pin "854b9a1ce93d9926018a0eb18e6e552769c5407d")
|
||||
(package! helpful :pin "584ecc887bb92133119f93a6716cdf7af0b51dca")
|
||||
(package! pcre2el :pin "0b5b2a2c173aab3fd14aac6cf5e90ad3bf58fa7d")
|
||||
(package! smartparens :pin "63695c64233d215a92bf08e762f643cdb595bdd9")
|
||||
(package! so-long
|
||||
: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")
|
||||
: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")
|
||||
|
||||
;; core-projects.el
|
||||
(package! projectile :pin "c31bd41c0b9d6fba8837ebfd3a31dec0b3cd73c6")
|
||||
|
||||
;; core-keybinds.el
|
||||
(package! general :pin "a0b17d207badf462311b2eef7c065b884462cb7c")
|
||||
(package! which-key :pin "428aedfce0157920814fbb2ae5d00b4aea89df88")
|
|
@ -0,0 +1,29 @@
|
|||
Please read through the following before you submit your issue.
|
||||
|
||||
+ [ ] Running `make` (then restarting Emacs) did not fix my issue
|
||||
+ [ ] If I have byte-compiled, I've tried recompiling with `make compile`
|
||||
+ [ ] If I changed the version of Emacs installed, I've recompiled by plugins
|
||||
with `make compile-elpa`
|
||||
+ [ ] I ran `make doctor` and it produced no leads
|
||||
+ [ ] My issue cannot be found [on the wiki](/docs/troubleshoot.org)
|
||||
+ [ ] I filled out the four fields in the template below
|
||||
|
||||
-------------------------------------------------------------------
|
||||
|
||||
### Observed behavior
|
||||
|
||||
<!-- What happened -->
|
||||
|
||||
### Expected behavior
|
||||
|
||||
<!-- What *should* have happened -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!-- Tell us how to reproduce the issue in steps -->
|
||||
|
||||
### Extra details
|
||||
|
||||
<!-- Include backtraces & screenshots if possible -->
|
||||
|
||||
-------------------------------------------------------------------
|
|
@ -0,0 +1,19 @@
|
|||
But before you doom yourself, here are some things you should know:
|
||||
|
||||
1. Don't forget to run 'doom sync', then restart Emacs, after modifying
|
||||
~/.doom.d/init.el or ~/.doom.d/packages.el.
|
||||
|
||||
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'!
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
4. Access Doom's documentation from within Emacs via 'SPC h d h' or 'C-h d h'
|
||||
(or 'M-x doom/help')
|
||||
|
||||
Have fun!
|
|
@ -0,0 +1,12 @@
|
|||
;; Welcome to the sandbox!
|
||||
;;
|
||||
;; This is a test bed for running Emacs Lisp in another instance of Emacs that
|
||||
;; has varying amounts of Doom loaded:
|
||||
;;
|
||||
;; - vanilla Emacs (nothing loaded) \[doom--run-vanilla-emacs]
|
||||
;; - vanilla Doom (only Doom core) \[doom--run-vanilla-doom]
|
||||
;; - Doom + modules - your private config \[doom--run-vanilla-doom+]
|
||||
;; - Doom + modules + your private config \[doom--run-full-doom]
|
||||
;;
|
||||
;; This is done without sacrificing access to installed packages. Use the sandbox
|
||||
;; to reproduce bugs and determine if Doom is to blame.
|
|
@ -0,0 +1,54 @@
|
|||
;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
;; Place your private configuration here! Remember, you do not need to run 'doom
|
||||
;; sync' after modifying this file!
|
||||
|
||||
|
||||
;; Some functionality uses this to identify you, e.g. GPG configuration, email
|
||||
;; clients, file templates and snippets.
|
||||
(setq user-full-name "John Doe"
|
||||
user-mail-address "john@doe.com")
|
||||
|
||||
;; Doom exposes five (optional) variables for controlling fonts in Doom. Here
|
||||
;; are the three important ones:
|
||||
;;
|
||||
;; + `doom-font'
|
||||
;; + `doom-variable-pitch-font'
|
||||
;; + `doom-big-font' -- used for `doom-big-font-mode'; use this for
|
||||
;; presentations or streaming.
|
||||
;;
|
||||
;; They all accept either a font-spec, font string ("Input Mono-12"), or xlfd
|
||||
;; font string. You generally only need these two:
|
||||
;; (setq doom-font (font-spec :family "monospace" :size 12 :weight 'semi-light)
|
||||
;; doom-variable-pitch-font (font-spec :family "sans" :size 13))
|
||||
|
||||
;; There are two ways to load a theme. Both assume the theme is installed and
|
||||
;; available. You can either set `doom-theme' or manually load a theme with the
|
||||
;; `load-theme' function. This is the default:
|
||||
(setq doom-theme 'doom-one)
|
||||
|
||||
;; If you use `org' and don't want your org files in the default location below,
|
||||
;; change `org-directory'. It must be set before org loads!
|
||||
(setq org-directory "~/org/")
|
||||
|
||||
;; This determines the style of line numbers in effect. If set to `nil', line
|
||||
;; numbers are disabled. For relative line numbers, set this to `relative'.
|
||||
(setq display-line-numbers-type t)
|
||||
|
||||
|
||||
;; Here are some additional functions/macros that could help you configure Doom:
|
||||
;;
|
||||
;; - `load!' for loading external *.el files relative to this one
|
||||
;; - `use-package!' for configuring packages
|
||||
;; - `after!' for running code after a package has loaded
|
||||
;; - `add-load-path!' for adding directories to the `load-path', relative to
|
||||
;; this file. Emacs searches the `load-path' when you load packages with
|
||||
;; `require' or `use-package'.
|
||||
;; - `map!' for binding new keys
|
||||
;;
|
||||
;; To get information about any of these functions/macros, move the cursor over
|
||||
;; the highlighted symbol at press 'K' (non-evil users must press 'C-c c k').
|
||||
;; This will open documentation for it, including demos of how they are used.
|
||||
;;
|
||||
;; You can also try 'gd' (or 'C-c c d') to jump to their definition and see how
|
||||
;; they are implemented.
|
|
@ -0,0 +1,50 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; $DOOMDIR/packages.el
|
||||
|
||||
;; To install a package with Doom you must declare them here and run 'doom sync'
|
||||
;; on the command line, then restart Emacs for the changes to take effect -- or
|
||||
;; use 'M-x doom/reload'.
|
||||
|
||||
|
||||
;; To install SOME-PACKAGE from MELPA, ELPA or emacsmirror:
|
||||
;(package! some-package)
|
||||
|
||||
;; To install a package directly from a remote git repo, you must specify a
|
||||
;; `:recipe'. You'll find documentation on what `:recipe' accepts here:
|
||||
;; https://github.com/raxod502/straight.el#the-recipe-format
|
||||
;(package! another-package
|
||||
; :recipe (:host github :repo "username/repo"))
|
||||
|
||||
;; If the package you are trying to install does not contain a PACKAGENAME.el
|
||||
;; file, or is located in a subdirectory of the repo, you'll need to specify
|
||||
;; `:files' in the `:recipe':
|
||||
;(package! this-package
|
||||
; :recipe (:host github :repo "username/repo"
|
||||
; :files ("some-file.el" "src/lisp/*.el")))
|
||||
|
||||
;; If you'd like to disable a package included with Doom, you can do so here
|
||||
;; with the `:disable' property:
|
||||
;(package! builtin-package :disable t)
|
||||
|
||||
;; You can override the recipe of a built in package without having to specify
|
||||
;; all the properties for `:recipe'. These will inherit the rest of its recipe
|
||||
;; from Doom or MELPA/ELPA/Emacsmirror:
|
||||
;(package! builtin-package :recipe (:nonrecursive t))
|
||||
;(package! builtin-package-2 :recipe (:repo "myfork/package"))
|
||||
|
||||
;; Specify a `:branch' to install a package from a particular branch or tag.
|
||||
;; This is required for some packages whose default branch isn't 'master' (which
|
||||
;; our package manager can't deal with; see raxod502/straight.el#279)
|
||||
;(package! builtin-package :recipe (:branch "develop"))
|
||||
|
||||
;; Use `:pin' to specify a particular commit to install.
|
||||
;(package! builtin-package :pin "1a2b3c4d5e")
|
||||
|
||||
|
||||
;; Doom's packages are pinned to a specific commit and updated from release to
|
||||
;; release. The `unpin!' macro allows you to unpin single packages...
|
||||
;(unpin! pinned-package)
|
||||
;; ...or multiple packages
|
||||
;(unpin! pinned-package another-pinned-package)
|
||||
;; ...Or *all* packages (NOT RECOMMENDED; will likely break things)
|
||||
;(unpin! t)
|
|
@ -0,0 +1,160 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-autoload-buffers.el
|
||||
|
||||
(describe "core/autoload/buffers"
|
||||
:var (a b c d)
|
||||
|
||||
(require 'core-projects)
|
||||
(load! "autoload/buffers" doom-core-dir)
|
||||
|
||||
(before-each
|
||||
(delete-other-windows)
|
||||
(setq a (switch-to-buffer (get-buffer-create "a"))
|
||||
b (get-buffer-create "b")
|
||||
c (get-buffer-create "c")
|
||||
d (get-buffer-create "d")))
|
||||
(after-each
|
||||
(kill-buffer a)
|
||||
(kill-buffer b)
|
||||
(kill-buffer c)
|
||||
(kill-buffer d))
|
||||
|
||||
(describe "buffer lists"
|
||||
(describe "doom-buffer-list"
|
||||
(it "should only see four buffers"
|
||||
(expect (doom-buffer-list) :to-contain-items (list a b c d)))))
|
||||
|
||||
;; TODO predicate tests
|
||||
(xdescribe "predicate functions"
|
||||
(describe "doom-dired-buffer-p")
|
||||
(describe "doom-special-buffer-p")
|
||||
(describe "doom-temp-buffer-p")
|
||||
(describe "doom-visible-buffer-p")
|
||||
(describe "doom-buried-buffer-p")
|
||||
(describe "doom-non-file-visiting-buffer-p")
|
||||
(describe "doom-dired-buffer-p")
|
||||
(describe "doom-buffer-frame-predicate"))
|
||||
|
||||
(describe "doom-project-buffer-list"
|
||||
:var (projectile-projects-cache-time projectile-projects-cache)
|
||||
(before-all (require 'projectile))
|
||||
(after-all (unload-feature 'projectile t))
|
||||
|
||||
(before-each
|
||||
(with-current-buffer a (setq default-directory doom-emacs-dir))
|
||||
(with-current-buffer b (setq default-directory doom-core-dir))
|
||||
(with-current-buffer c (setq default-directory "/tmp/"))
|
||||
(with-current-buffer d (setq default-directory "~"))
|
||||
(projectile-mode +1))
|
||||
(after-each
|
||||
(projectile-mode -1))
|
||||
|
||||
(it "returns buffers in the same project"
|
||||
(with-current-buffer a
|
||||
(expect (doom-project-buffer-list)
|
||||
:to-contain-items (list a b))))
|
||||
|
||||
(it "returns all buffers if not in a project"
|
||||
(with-current-buffer c
|
||||
(expect (doom-project-buffer-list)
|
||||
:to-have-same-items-as (buffer-list)))))
|
||||
|
||||
(describe "doom-fallback-buffer"
|
||||
(it "returns a live buffer"
|
||||
(expect (buffer-live-p (doom-fallback-buffer))))
|
||||
|
||||
(it "returns the scratch buffer"
|
||||
(expect (doom-fallback-buffer) :to-equal (get-buffer "*scratch*"))))
|
||||
|
||||
(describe "real buffers"
|
||||
(before-each
|
||||
(with-current-buffer b (setq buffer-file-name "x"))
|
||||
(with-current-buffer c (rename-buffer "*C*")))
|
||||
|
||||
(describe "doom-mark-buffer-as-real-h"
|
||||
(with-current-buffer a
|
||||
(doom-mark-buffer-as-real-h)
|
||||
(expect (buffer-local-value 'doom-real-buffer-p a))))
|
||||
|
||||
(describe "doom-set-buffer-real"
|
||||
(it "sets `doom-real-buffer-p' buffer-locally"
|
||||
(doom-set-buffer-real a t)
|
||||
(expect (buffer-local-value 'doom-real-buffer-p a))))
|
||||
|
||||
(describe "doom-real-buffer-p"
|
||||
(it "returns t for buffers manually marked real"
|
||||
(doom-set-buffer-real a t)
|
||||
(expect (doom-real-buffer-p a)))
|
||||
(it "returns t for file-visiting buffers"
|
||||
(expect (doom-real-buffer-p b)))
|
||||
(it "returns nil for temporary buffers"
|
||||
(expect (doom-real-buffer-p c) :to-be nil)
|
||||
(expect (doom-real-buffer-p d) :to-be nil)))
|
||||
|
||||
(describe "doom-unreal-buffer-p"
|
||||
(it "returns t for unreal buffers"
|
||||
(expect (doom-unreal-buffer-p c))
|
||||
(expect (doom-unreal-buffer-p d)))
|
||||
(it "returns nil for real buffers"
|
||||
(doom-set-buffer-real a t)
|
||||
(expect (not (doom-unreal-buffer-p a)))
|
||||
(expect (not (doom-unreal-buffer-p b)))))
|
||||
|
||||
(describe "doom-real-buffer-list"
|
||||
(it "returns only real buffers"
|
||||
(expect (doom-real-buffer-list) :to-contain-items (list a b)))))
|
||||
|
||||
(describe "buffer/window management"
|
||||
(describe "buffer search methods"
|
||||
(before-each
|
||||
(with-current-buffer a (lisp-mode))
|
||||
(with-current-buffer b (text-mode))
|
||||
(with-current-buffer c (text-mode))
|
||||
(split-window)
|
||||
(switch-to-buffer b))
|
||||
|
||||
(describe "doom-matching-buffers"
|
||||
(it "can match buffers by regexp"
|
||||
(expect (doom-matching-buffers "^[ac]$") :to-have-same-items-as (list a c))))
|
||||
|
||||
(describe "doom-buffers-in-mode"
|
||||
(it "can match buffers by major-mode"
|
||||
(expect (doom-buffers-in-mode 'text-mode) :to-have-same-items-as (list b c))))
|
||||
|
||||
(describe "doom-buried-buffers"
|
||||
(it "can find all buried buffers"
|
||||
(expect (doom-buried-buffers) :to-contain-items (list c d))))
|
||||
|
||||
(describe "doom-visible-buffers"
|
||||
(it "can find all visible buffers"
|
||||
(expect (doom-visible-buffers)
|
||||
:to-have-same-items-as (list a b))))
|
||||
|
||||
(describe "doom-visible-windows"
|
||||
(it "can find all visible windows"
|
||||
(expect (doom-visible-windows)
|
||||
:to-have-same-items-as
|
||||
(mapcar #'get-buffer-window (list a b))))))
|
||||
|
||||
(describe "killing buffers/windows"
|
||||
(describe "doom-kill-buffer-and-windows"
|
||||
(before-each
|
||||
(split-window) (switch-to-buffer b)
|
||||
(split-window) (switch-to-buffer a))
|
||||
|
||||
(it "kills the selected buffers and all its windows"
|
||||
(doom-kill-buffer-and-windows a)
|
||||
(expect (buffer-live-p a) :to-be nil)
|
||||
(expect (length (doom-visible-windows)) :to-be 1)))
|
||||
|
||||
;; TODO
|
||||
(xdescribe "doom-fixup-windows")
|
||||
(xdescribe "doom-kill-buffer-fixup-windows")
|
||||
(xdescribe "doom-kill-buffers-fixup-windows"))
|
||||
|
||||
(xdescribe "commands"
|
||||
(describe "doom/kill-all-buffers")
|
||||
(describe "doom/kill-other-buffers")
|
||||
(describe "doom/kill-matching-buffers")
|
||||
(describe "doom/kill-buried-buffers")
|
||||
(describe "doom/kill-project-buffers"))))
|
|
@ -0,0 +1,164 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-autoload-files.el
|
||||
|
||||
(describe "core/autoload/files"
|
||||
|
||||
(load! "autoload/files" doom-core-dir)
|
||||
|
||||
(describe "library"
|
||||
(describe "file-exists-p!"
|
||||
(it "is a (quasi) drop-in replacement for `file-exists-p'"
|
||||
(let ((default-directory doom-emacs-dir)
|
||||
(init-file "init.el"))
|
||||
(expect (file-exists-p "init.el"))
|
||||
(expect (and (file-exists-p! "init.el")
|
||||
(file-exists-p "init.el")))
|
||||
(expect (and (file-exists-p! init-file)
|
||||
(file-exists-p init-file)))
|
||||
(expect (and (file-exists-p! doom-emacs-dir)
|
||||
(file-exists-p doom-emacs-dir)))
|
||||
(expect (and (not (file-exists-p! "/cant/possibly/exist/please/dont/exist"))
|
||||
(not (file-exists-p "/cant/possibly/exist/please/dont/exist"))))))
|
||||
|
||||
(it "returns the file path if it exists"
|
||||
(expect (file-exists-p! "init.example.el"
|
||||
doom-emacs-dir)
|
||||
:to-equal (expand-file-name "init.example.el" doom-emacs-dir)))
|
||||
|
||||
(it "understands compound statements"
|
||||
(let ((default-directory doom-emacs-dir))
|
||||
(expect (file-exists-p! (and "init.el" "init.example.el")))
|
||||
(expect (file-exists-p! (or "doesnotexist" "init.example.el")))
|
||||
(expect (not (file-exists-p! (or "doesnotexist" "DOESNOTEXIST")))))
|
||||
(expect (file-exists-p! (and "init.el" "init.example.el")
|
||||
doom-emacs-dir))
|
||||
(expect (file-exists-p! (and "init.el" "init.example.el")
|
||||
doom-emacs-dir))
|
||||
(expect (file-exists-p! (or "doesnotexist" "init.example.el")
|
||||
doom-emacs-dir))
|
||||
(expect (not (file-exists-p! (or "doesnotexist" "DOESNOTEXIST")
|
||||
doom-emacs-dir))))
|
||||
|
||||
(it "understands nested compound statements"
|
||||
(expect (file-exists-p! (and "init.el" "init.example.el"
|
||||
(or "doesnotexist" "LICENSE"))
|
||||
doom-emacs-dir))
|
||||
(expect (file-exists-p! (and "init.el" "init.example.el"
|
||||
(and "LICENSE" "README.md"
|
||||
(or "doesnotexist"
|
||||
"early-init.el")))
|
||||
doom-emacs-dir))
|
||||
(expect (file-exists-p! (and "init.el" "init.example.el"
|
||||
(or "edoesnotexist" "DOESNOTEXIST"
|
||||
(and "idontexist"
|
||||
"doanyofusexist?")))
|
||||
doom-emacs-dir)
|
||||
:to-be nil))
|
||||
|
||||
(it "returns the last form if a compound file check succeeds"
|
||||
(expect (file-exists-p! (and "init.el" "init.example.el"
|
||||
(or "doesnotexist" "LICENSE"))
|
||||
doom-emacs-dir)
|
||||
:to-equal (expand-file-name "LICENSE" doom-emacs-dir))
|
||||
(expect (file-exists-p! (and "init.el" "init.example.el"
|
||||
(or (or "doesnotexist" "DOESNOTEXIST")
|
||||
"doanyofusreallyexist"
|
||||
(or "cantexist" "LICENSE")))
|
||||
doom-emacs-dir)
|
||||
:to-equal (expand-file-name "LICENSE" doom-emacs-dir)))
|
||||
|
||||
(it "disregards the directory argument if given absolute path"
|
||||
(expect (file-exists-p! "/tmp" "/directory/that/doesnt/exist"))
|
||||
(expect (file-exists-p! doom-core-dir "/directory/that/doesnt/exist"))
|
||||
(expect (file-exists-p! (and "/tmp" doom-core-dir) "/directory/that/doesnt/exist"))
|
||||
(expect (file-exists-p! (or "/tmp" doom-core-dir) "/directory/that/doesnt/exist")))
|
||||
|
||||
(it "interpolates variables"
|
||||
(let ((file-1 "init.el")
|
||||
(file-2 "init.example.el")
|
||||
(file-3 "LICENSE")
|
||||
(file-404 "doesnotexistlikenoreally"))
|
||||
(expect (file-exists-p! file-1 doom-emacs-dir))
|
||||
(expect (file-exists-p! (and file-1 file-2) doom-emacs-dir))
|
||||
(expect (file-exists-p! (and file-1 (or file-404 file-2)) doom-emacs-dir))
|
||||
(expect (file-exists-p! (or (and file-404 file-2) (and file-3 file-1))
|
||||
doom-emacs-dir))))
|
||||
|
||||
(it "interpolates forms"
|
||||
(cl-letf (((symbol-function 'getfilename)
|
||||
(lambda () "init.example.el")))
|
||||
(expect (file-exists-p! (and (or (if nil "init.el" "doesnotexist")
|
||||
(getfilename))
|
||||
"LICENSE")
|
||||
doom-emacs-dir)
|
||||
:to-equal (expand-file-name "LICENSE" doom-emacs-dir)))))
|
||||
|
||||
;; TODO
|
||||
(xdescribe "doom-glob")
|
||||
(xdescribe "doom-path")
|
||||
(xdescribe "doom-dir")
|
||||
(xdescribe "doom-files-in")
|
||||
(xdescribe "doom-file-size")
|
||||
(xdescribe "doom-directory-size")
|
||||
(xdescribe "doom-file-cookie-p"))
|
||||
|
||||
(describe "interactive file operations"
|
||||
:var (src dest projectile-projects-cache-time projectile-projects-cache)
|
||||
|
||||
(require 'core-projects)
|
||||
(require 'projectile)
|
||||
|
||||
(before-each
|
||||
(setq src (make-temp-file "test-src")
|
||||
existing (make-temp-file "test-existing")
|
||||
dest (expand-file-name "test-dest" temporary-file-directory))
|
||||
(quiet! (find-file-literally src))
|
||||
(spy-on 'y-or-n-p :and-return-value nil)
|
||||
(projectile-mode +1))
|
||||
|
||||
(after-each
|
||||
(projectile-mode -1)
|
||||
(switch-to-buffer (doom-fallback-buffer))
|
||||
(ignore-errors (delete-file src))
|
||||
(ignore-errors (delete-file existing))
|
||||
(ignore-errors (delete-file dest)))
|
||||
|
||||
(describe "move-this-file"
|
||||
(it "won't move to itself"
|
||||
(expect (quiet! (doom/move-this-file src)) :to-throw))
|
||||
(it "will move to another file"
|
||||
(expect (quiet! (doom/move-this-file dest t)))
|
||||
(expect (file-exists-p dest))
|
||||
(expect (file-exists-p src) :to-be nil))
|
||||
(it "will prompt if overwriting a file"
|
||||
(quiet! (doom/move-this-file existing))
|
||||
(expect 'y-or-n-p :to-have-been-called-times 1)
|
||||
(expect (file-exists-p src))))
|
||||
|
||||
(describe "copy-this-file"
|
||||
(it "refuses to copy to itself"
|
||||
(expect (quiet! (doom/copy-this-file src)) :to-throw))
|
||||
(it "copies to another file"
|
||||
(expect (quiet! (doom/copy-this-file dest t)))
|
||||
(expect (file-exists-p! src dest)))
|
||||
(it "prompts if overwriting a file"
|
||||
(quiet! (doom/copy-this-file existing))
|
||||
(expect 'y-or-n-p :to-have-been-called-times 1)))
|
||||
|
||||
(describe "delete-this-file"
|
||||
(it "fails gracefully on non-existent files"
|
||||
(expect (quiet! (doom/delete-this-file dest)) :to-throw))
|
||||
(it "deletes existing files"
|
||||
(quiet! (doom/delete-this-file existing t))
|
||||
(expect (file-exists-p existing) :to-be nil))
|
||||
(it "prompts to delete any existing file"
|
||||
(quiet! (doom/delete-this-file existing))
|
||||
(expect 'y-or-n-p :to-have-been-called-times 1))))
|
||||
|
||||
(xdescribe "sudo {this,find} file"
|
||||
(before-each
|
||||
(spy-on 'find-file :and-return-value nil)
|
||||
(spy-on 'find-alternate-file :and-return-value nil))
|
||||
|
||||
(describe "doom/sudo-find-file")
|
||||
(describe "doom/sudo-this-file")))
|
|
@ -0,0 +1,44 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-autoload-message.el
|
||||
|
||||
(describe "core/autoload/format"
|
||||
(describe "format!"
|
||||
:var (doom-output-backend)
|
||||
(before-all
|
||||
(setq doom-output-backend 'ansi))
|
||||
|
||||
(it "should be a drop-in replacement for `format'"
|
||||
(expect (format! "Hello %s" "World")
|
||||
:to-equal "Hello World"))
|
||||
|
||||
(it "supports ansi coloring in noninteractive sessions"
|
||||
(expect (format! (red "Hello %s") "World")
|
||||
:to-equal "[31mHello World[0m"))
|
||||
|
||||
(it "supports text properties in interactive sessions"
|
||||
(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)))))
|
||||
|
||||
(it "supports nested color specs"
|
||||
(expect (format! (bold (red "Hello %s")) "World")
|
||||
:to-equal (format "\e[%dm%s\e[0m" 1
|
||||
(format "\e[%dm%s\e[0m" 31 "Hello World")))
|
||||
(expect (format! (on-red (bold "Hello %s")) "World")
|
||||
:to-equal (format "\e[%dm%s\e[0m" 41
|
||||
(format "\e[%dm%s\e[0m" 1 "Hello World")))
|
||||
(expect (format! (dark (white "Hello %s")) "World")
|
||||
:to-equal (format "\e[%dm%s\e[0m" 2
|
||||
(format "\e[%dm%s\e[0m" 37 "Hello World"))))
|
||||
|
||||
(it "supports dynamic color apply syntax"
|
||||
(expect (format! (color 'red "Hello %s") "World")
|
||||
:to-equal (format! (red "Hello %s") "World"))
|
||||
(expect (format! (color (if nil 'red 'blue) "Hello %s") "World")
|
||||
:to-equal (format! (blue "Hello %s") "World"))))
|
||||
|
||||
(xdescribe "insert!")
|
||||
(xdescribe "print!")
|
||||
(xdescribe "print-group!")
|
||||
(xdescribe "error!")
|
||||
(xdescribe "user-error!"))
|
|
@ -0,0 +1,5 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-autoload-package.el
|
||||
;;;###if nil
|
||||
|
||||
(xdescribe "core/autoload/packages")
|
|
@ -0,0 +1,256 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core-keybinds.el
|
||||
|
||||
(describe "core/keybinds"
|
||||
(require 'core-keybinds)
|
||||
|
||||
;; FIXME test against their side effects rather than their implementation
|
||||
(describe "map!"
|
||||
:var (doom--map-evil-p states-alist)
|
||||
(before-each
|
||||
(setq doom--map-evil-p t
|
||||
states-alist '((:n . normal)
|
||||
(:v . visual)
|
||||
(:i . insert)
|
||||
(:e . emacs)
|
||||
(:o . operator)
|
||||
(:m . motion)
|
||||
(:r . replace))))
|
||||
|
||||
(describe "Single keybinds"
|
||||
(it "binds a global key"
|
||||
(expect '(map! "C-." #'a)
|
||||
:to-expand-into '(general-define-key "C-." #'a)))
|
||||
|
||||
(it "binds a key in one evil state"
|
||||
(dolist (state states-alist)
|
||||
(expect `(map! ,(car state) "C-." #'a)
|
||||
:to-expand-into
|
||||
`(general-define-key :states ',(cdr state) "C-." #'a))))
|
||||
|
||||
(it "binds a key in multiple evil states"
|
||||
(expect '(map! :nvi "C-." #'a)
|
||||
:to-expand-into
|
||||
'(progn (general-define-key :states 'insert "C-." #'a)
|
||||
(general-define-key :states 'visual "C-." #'a)
|
||||
(general-define-key :states 'normal "C-." #'a))))
|
||||
|
||||
(it "binds evil keybinds together with global keybinds"
|
||||
(expect '(map! :ng "C-." #'a)
|
||||
:to-expand-into
|
||||
'(progn
|
||||
(general-define-key :states 'normal "C-." #'a)
|
||||
(general-define-key "C-." #'a)))))
|
||||
|
||||
(describe "Multiple keybinds"
|
||||
(it "binds global keys and preserves order"
|
||||
(expect '(map! "C-." #'a "C-," #'b "C-/" #'c)
|
||||
:to-expand-into
|
||||
'(general-define-key "C-." #'a "C-," #'b "C-/" #'c)))
|
||||
|
||||
(it "binds multiple keybinds in an evil state and preserve order"
|
||||
(dolist (state states-alist)
|
||||
(expect `(map! ,(car state) "a" #'a
|
||||
,(car state) "b" #'b
|
||||
,(car state) "c" #'c)
|
||||
:to-expand-into
|
||||
`(general-define-key :states ',(cdr state)
|
||||
"a" #'a
|
||||
"b" #'b
|
||||
"c" #'c))))
|
||||
|
||||
(it "binds multiple keybinds in different evil states"
|
||||
(expect `(map! :n "a" #'a
|
||||
:n "b" #'b
|
||||
:n "e" #'e
|
||||
:v "c" #'c
|
||||
:i "d" #'d)
|
||||
:to-expand-into
|
||||
`(progn (general-define-key :states 'insert "d" #'d)
|
||||
(general-define-key :states 'visual "c" #'c)
|
||||
(general-define-key :states 'normal "a" #'a "b" #'b "e" #'e))))
|
||||
|
||||
(it "groups multi-state keybinds while preserving same-group key order"
|
||||
(expect `(map! :n "a" #'a
|
||||
:v "c" #'c
|
||||
:n "b" #'b
|
||||
:i "d" #'d
|
||||
:n "e" #'e)
|
||||
:to-expand-into
|
||||
`(progn (general-define-key :states 'insert "d" #'d)
|
||||
(general-define-key :states 'visual "c" #'c)
|
||||
(general-define-key :states 'normal "a" #'a "b" #'b "e" #'e))))
|
||||
|
||||
(it "binds multiple keybinds in multiple evil states"
|
||||
(expect `(map! :nvi "a" #'a
|
||||
:nvi "b" #'b
|
||||
:nvi "c" #'c)
|
||||
:to-expand-into
|
||||
'(progn (general-define-key :states 'insert "a" #'a "b" #'b "c" #'c)
|
||||
(general-define-key :states 'visual "a" #'a "b" #'b "c" #'c)
|
||||
(general-define-key :states 'normal "a" #'a "b" #'b "c" #'c)))))
|
||||
|
||||
(describe "Nested keybinds"
|
||||
(it "binds global keys"
|
||||
(expect '(map! "C-." #'a
|
||||
("C-a" #'b)
|
||||
("C-x" #'c))
|
||||
:to-expand-into
|
||||
'(progn (general-define-key "C-." #'a)
|
||||
(general-define-key "C-a" #'b)
|
||||
(general-define-key "C-x" #'c))))
|
||||
|
||||
(it "binds nested evil keybinds"
|
||||
(expect '(map! :n "C-." #'a
|
||||
(:n "C-a" #'b)
|
||||
(:n "C-x" #'c))
|
||||
:to-expand-into
|
||||
'(progn (general-define-key :states 'normal "C-." #'a)
|
||||
(general-define-key :states 'normal "C-a" #'b)
|
||||
(general-define-key :states 'normal "C-x" #'c))))
|
||||
|
||||
(it "binds global keybinds in between evil keybinds"
|
||||
(expect '(map! :n "a" #'a
|
||||
"b" #'b
|
||||
:n "c" #'c)
|
||||
:to-expand-into
|
||||
'(progn (general-define-key "b" #'b)
|
||||
(general-define-key :states 'normal "a" #'a "c" #'c)))))
|
||||
|
||||
;;
|
||||
(describe "Properties"
|
||||
(describe ":after"
|
||||
(it "wraps `general-define-key' in a `after!' block"
|
||||
(dolist (form '((map! :after helm "a" #'a "b" #'b)
|
||||
(map! (:after helm "a" #'a "b" #'b))))
|
||||
(expect form :to-expand-into '(after! helm (general-define-key "a" #'a "b" #'b))))
|
||||
(expect '(map! "a" #'a (:after helm "b" #'b "c" #'c))
|
||||
:to-expand-into
|
||||
'(progn
|
||||
(general-define-key "a" #'a)
|
||||
(after! helm
|
||||
(general-define-key "b" #'b "c" #'c))))
|
||||
(expect '(map! (:after helm "b" #'b "c" #'c) "a" #'a)
|
||||
:to-expand-into
|
||||
'(progn
|
||||
(after! helm
|
||||
(general-define-key "b" #'b "c" #'c))
|
||||
(general-define-key "a" #'a))))
|
||||
|
||||
(it "nests `after!' blocks"
|
||||
(expect '(map! :after x "a" #'a
|
||||
(:after y "b" #'b
|
||||
(:after z "c" #'c)))
|
||||
:to-expand-into
|
||||
'(after! x
|
||||
(progn
|
||||
(general-define-key "a" #'a)
|
||||
(after! y
|
||||
(progn
|
||||
(general-define-key "b" #'b)
|
||||
(after! z
|
||||
(general-define-key "c" #'c))))))))
|
||||
|
||||
(it "nests `after!' blocks in other nested blocks"
|
||||
(expect '(map! :after x "a" #'a
|
||||
(:when t "b" #'b
|
||||
(:after z "c" #'c)))
|
||||
:to-expand-into
|
||||
'(after! x
|
||||
(progn
|
||||
(general-define-key "a" #'a)
|
||||
(when t
|
||||
(progn
|
||||
(general-define-key "b" #'b)
|
||||
(after! z (general-define-key "c" #'c)))))))))
|
||||
|
||||
(describe ":desc"
|
||||
(it "add a :which-key property to a keybind's DEF"
|
||||
(expect '(map! :desc "A" "a" #'a)
|
||||
:to-expand-into
|
||||
`(general-define-key "a" (list :def #'a :which-key "A")))))
|
||||
|
||||
(describe ":when/:unless"
|
||||
(it "wraps keys in a conditional block"
|
||||
(dolist (prop '(:when :unless))
|
||||
(let ((prop-fn (intern (doom-keyword-name prop))))
|
||||
(expect `(map! ,prop t "a" #'a "b" #'b)
|
||||
:to-expand-into
|
||||
`(,prop-fn t (general-define-key "a" #'a "b" #'b)))
|
||||
(expect `(map! (,prop t "a" #'a "b" #'b))
|
||||
:to-expand-into
|
||||
`(,prop-fn t (general-define-key "a" #'a "b" #'b))))))
|
||||
|
||||
(it "nests conditional blocks"
|
||||
(expect '(map! (:when t "a" #'a (:when t "b" #'b)))
|
||||
:to-expand-into
|
||||
'(when t
|
||||
(progn (general-define-key "a" #'a)
|
||||
(when t (general-define-key "b" #'b)))))))
|
||||
|
||||
(describe ":leader"
|
||||
(it "uses leader definer"
|
||||
(expect '(map! :leader "a" #'a "b" #'b)
|
||||
:to-expand-into
|
||||
'(doom--define-leader-key "a" #'a "b" #'b)))
|
||||
|
||||
(it "it persists for nested keys"
|
||||
(expect '(map! :leader "a" #'a ("b" #'b))
|
||||
:to-expand-into
|
||||
'(progn (doom--define-leader-key "a" #'a)
|
||||
(doom--define-leader-key "b" #'b)))))
|
||||
|
||||
(describe ":localleader"
|
||||
(it "uses localleader definer"
|
||||
(expect '(map! :localleader "a" #'a "b" #'b)
|
||||
:to-expand-into
|
||||
'(define-localleader-key! "a" #'a "b" #'b)))
|
||||
|
||||
(it "it persists for nested keys"
|
||||
(expect '(map! :localleader "a" #'a ("b" #'b))
|
||||
:to-expand-into
|
||||
'(progn (define-localleader-key! "a" #'a)
|
||||
(define-localleader-key! "b" #'b)))))
|
||||
|
||||
(describe ":map/:keymap"
|
||||
(it "specifies a single keymap for keys"
|
||||
(expect '(map! :map emacs-lisp-mode-map "a" #'a)
|
||||
:to-expand-into
|
||||
'(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a)))
|
||||
|
||||
(it "specifies multiple keymap for keys"
|
||||
(expect '(map! :map (lisp-mode-map emacs-lisp-mode-map) "a" #'a)
|
||||
:to-expand-into
|
||||
'(general-define-key :keymaps '(lisp-mode-map emacs-lisp-mode-map) "a" #'a))))
|
||||
|
||||
(describe ":mode"
|
||||
(it "appends -map to MODE"
|
||||
(expect '(map! :mode emacs-lisp-mode "a" #'a)
|
||||
:to-expand-into
|
||||
'(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a))))
|
||||
|
||||
(describe ":prefix"
|
||||
(it "specifies a prefix for all keys"
|
||||
(expect '(map! :prefix "a" "x" #'x "y" #'y "z" #'z)
|
||||
:to-expand-into
|
||||
'(general-define-key :prefix "a" "x" #'x "y" #'y "z" #'z)))
|
||||
|
||||
(it "overwrites previous inline :prefix properties"
|
||||
(expect '(map! :prefix "a" "x" #'x "y" #'y :prefix "b" "z" #'z)
|
||||
:to-expand-into
|
||||
'(progn (general-define-key :prefix "a" "x" #'x "y" #'y)
|
||||
(general-define-key :prefix "b" "z" #'z))))
|
||||
|
||||
(it "accumulates keys when nested"
|
||||
(expect '(map! (:prefix "a" "x" #'x (:prefix "b" "x" #'x)))
|
||||
:to-expand-into
|
||||
`(progn (general-define-key :prefix "a" "x" #'x)
|
||||
(general-define-key :prefix (general--concat nil "a" "b")
|
||||
"x" #'x)))))
|
||||
|
||||
(describe ":textobj"
|
||||
(it "defines keys in evil-{inner,outer}-text-objects-map"
|
||||
(expect '(map! :textobj "a" #'inner #'outer)
|
||||
:to-expand-into
|
||||
'(map! (:map evil-inner-text-objects-map "a" #'inner)
|
||||
(:map evil-outer-text-objects-map "a" #'outer))))))))
|
|
@ -0,0 +1,271 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core-lib.el
|
||||
|
||||
(describe "core-lib"
|
||||
(before-all
|
||||
(require 'core-lib))
|
||||
|
||||
;; --- Helpers ----------------------------
|
||||
(describe "doom-unquote"
|
||||
(it "unquotes a quoted form"
|
||||
(expect (doom-unquote '(quote hello)) :to-be 'hello))
|
||||
(it "unquotes nested quoted forms"
|
||||
(expect (doom-unquote '(quote (quote (a b c)))) :to-equal '(a b c)))
|
||||
(it "unquotes function-quoted forms"
|
||||
(expect (doom-unquote '(function a)) :to-be 'a))
|
||||
(it "does nothing to unquoted forms"
|
||||
(expect (doom-unquote 'hello) :to-be 'hello)
|
||||
(expect (doom-unquote 5) :to-be 5)
|
||||
(expect (doom-unquote t) :to-be t)))
|
||||
|
||||
(describe "doom-enlist"
|
||||
(it "returns nil if given nil"
|
||||
(expect (doom-enlist nil) :to-be nil))
|
||||
(it "creates a list out of non-lists"
|
||||
(expect (doom-enlist 'a) :to-equal '(a)))
|
||||
(it "returns lists as-is"
|
||||
(expect (doom-enlist '(a)) :to-equal '(a))))
|
||||
|
||||
(describe "doom-keyword-intern"
|
||||
(it "returns a keyword"
|
||||
(expect (doom-keyword-intern "test") :to-equal :test))
|
||||
(it "errors if given anything but a string"
|
||||
(expect (doom-keyword-intern t) :to-throw 'wrong-type-argument)))
|
||||
|
||||
(describe "doom-keyword-name"
|
||||
(it "returns the string name of a keyword"
|
||||
(expect (doom-keyword-name :test) :to-equal "test"))
|
||||
(it "errors if given anything but a keyword"
|
||||
(expect (doom-keyword-name "test") :to-throw 'wrong-type-argument)))
|
||||
|
||||
(describe "doom-partial"
|
||||
(it "returns a closure"
|
||||
(expect (functionp (doom-partial #'+ 1))))
|
||||
(it "returns a partial closure"
|
||||
(expect (funcall (doom-partial #'+ 1) 2) :to-be 3)))
|
||||
|
||||
(describe "doom-rpartial"
|
||||
(it "returns a closure"
|
||||
(expect (functionp (doom-rpartial #'+ 1))))
|
||||
(it "returns a partial closure with right-aligned arguments"
|
||||
(expect (funcall (doom-rpartial #'/ 2) 10) :to-be 5)))
|
||||
|
||||
|
||||
;; --- Sugars -----------------------------
|
||||
(describe "lambda!"
|
||||
(it "returns an interactive function"
|
||||
(expect (commandp (lambda!)))
|
||||
(expect (funcall (lambda! 5)) :to-equal 5)))
|
||||
|
||||
(describe "lambda!!"
|
||||
(it "returns an interactive function with a prefix argument"
|
||||
(expect (commandp (lambda! #'ignore t)))
|
||||
(expect (funcall (lambda!! (lambda (arg)
|
||||
(interactive "P")
|
||||
arg)
|
||||
5))
|
||||
:to-equal 5)))
|
||||
|
||||
(describe "file!"
|
||||
(it "returns the executing file"
|
||||
(expect (eval-and-compile (file!))
|
||||
:to-equal
|
||||
(eval-and-compile load-file-name))))
|
||||
|
||||
(describe "dir!"
|
||||
(it "returns the executing directory"
|
||||
(expect (eval-and-compile (dir!))
|
||||
:to-equal
|
||||
(eval-and-compile
|
||||
(directory-file-name (file-name-directory load-file-name))))))
|
||||
|
||||
(describe "pushnew!"
|
||||
(it "pushes values onto a list symbol, in order"
|
||||
(let ((a '(1 2 3)))
|
||||
(expect (pushnew! a 9 8 7)
|
||||
:to-equal '(7 8 9 1 2 3))))
|
||||
(it "only adds values that aren't already in the list"
|
||||
(let ((a '(1 symbol 3.14 "test")))
|
||||
(expect (pushnew! a "test" 'symbol 3.14 1)
|
||||
:to-equal '(1 symbol 3.14 "test")))))
|
||||
|
||||
(describe "prependq!"
|
||||
(it "prepends a list to a list symbol"
|
||||
(let ((list '(a b c)))
|
||||
(expect (prependq! list '(d e f))
|
||||
:to-equal '(d e f a b c)))))
|
||||
|
||||
(describe "append!"
|
||||
(it "appends a list to a list symbol"
|
||||
(let ((list '(a b c)))
|
||||
(expect (appendq! 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)))
|
||||
(delq! 'b list)
|
||||
(expect list :to-equal '(a c))))
|
||||
(it "delete's an element from an alist by key"
|
||||
(let ((alist '((a 1) (b 2) (c 3))))
|
||||
(delq! 'b alist 'assq)
|
||||
(expect alist :to-equal '((a 1) (c 3))))))
|
||||
|
||||
(describe "hooks"
|
||||
(describe "add-hook!"
|
||||
:var (fake-mode-hook other-mode-hook some-mode-hook)
|
||||
(before-each
|
||||
(setq fake-mode-hook '(first-hook)
|
||||
other-mode-hook nil
|
||||
some-mode-hook '(first-hook second-hook)))
|
||||
|
||||
(it "resolves quoted hooks literally"
|
||||
(expect '(add-hook! 'fake-mode-hook #'ignore) :to-expand-into
|
||||
`(add-hook 'fake-mode-hook #'ignore nil nil)))
|
||||
(it "resolves unquoted modes to their hook variables"
|
||||
(expect '(add-hook! fake-mode #'ignore) :to-expand-into
|
||||
`(add-hook 'fake-mode-hook #'ignore nil nil)))
|
||||
|
||||
(it "adds one-to-one hook"
|
||||
(add-hook! fake-mode #'hook-2)
|
||||
(add-hook! 'fake-mode-hook #'hook-1)
|
||||
(expect fake-mode-hook :to-equal '(hook-1 hook-2 first-hook)))
|
||||
|
||||
(it "adds one-to-many hook"
|
||||
(add-hook! (fake-mode other-mode some-mode) #'hook-2)
|
||||
(add-hook! '(fake-mode-hook other-mode-hook some-mode-hook) #'hook-1)
|
||||
(add-hook! (fake-mode other-mode some-mode) :append #'last-hook)
|
||||
(expect fake-mode-hook :to-equal '(hook-1 hook-2 first-hook last-hook))
|
||||
(expect other-mode-hook :to-equal '(hook-1 hook-2 last-hook))
|
||||
(expect some-mode-hook :to-equal '(hook-1 hook-2 first-hook second-hook last-hook)))
|
||||
|
||||
(it "adds many-to-many hooks and preserve provided order"
|
||||
(add-hook! (fake-mode other-mode some-mode) #'(hook-3 hook-4))
|
||||
(add-hook! '(fake-mode-hook other-mode-hook some-mode-hook) #'(hook-1 hook-2))
|
||||
(add-hook! '(fake-mode-hook other-mode-hook some-mode-hook) :append #'(last-hook-1 last-hook-2))
|
||||
(expect fake-mode-hook :to-equal '(hook-1 hook-2 hook-3 hook-4 first-hook last-hook-1 last-hook-2))
|
||||
(expect other-mode-hook :to-equal '(hook-1 hook-2 hook-3 hook-4 last-hook-1 last-hook-2))
|
||||
(expect some-mode-hook :to-equal '(hook-1 hook-2 hook-3 hook-4 first-hook second-hook last-hook-1 last-hook-2)))
|
||||
|
||||
(it "adds implicit lambda to one hook"
|
||||
(add-hook! fake-mode (progn))
|
||||
(add-hook! 'other-mode-hook (ignore))
|
||||
(add-hook! 'some-mode-hook :append (ignore))
|
||||
(expect (caar fake-mode-hook) :to-be 'lambda)
|
||||
(expect (caar other-mode-hook) :to-be 'lambda)
|
||||
(expect (caar (last other-mode-hook)) :to-be 'lambda))
|
||||
|
||||
(it "handles inline defuns as hook symbols"
|
||||
(add-hook! fake-mode (defun hook-a ()))
|
||||
(add-hook! 'other-mode-hook
|
||||
(defun hook-b ())
|
||||
(defun hook-c ()))
|
||||
(expect (car fake-mode-hook) :to-be 'hook-a)
|
||||
(expect other-mode-hook :to-equal '(hook-b hook-c))))
|
||||
|
||||
(describe "remove-hook!"
|
||||
:var (fake-mode-hook)
|
||||
(before-each
|
||||
(setq fake-mode-hook '(first-hook second-hook third-hook fourth-hook)))
|
||||
(it "removes one hook"
|
||||
(remove-hook! fake-mode #'third-hook)
|
||||
(remove-hook! 'fake-mode-hook #'second-hook)
|
||||
(expect fake-mode-hook :to-equal '(first-hook fourth-hook)))
|
||||
(it "removes multiple hooks"
|
||||
(remove-hook! fake-mode #'(first-hook third-hook))
|
||||
(remove-hook! 'fake-mode-hook #'(second-hook fourth-hook))
|
||||
(expect fake-mode-hook :to-be nil)))
|
||||
|
||||
(describe "add-transient-hook!"
|
||||
(it "adds a transient function to hooks"
|
||||
(let (hooks value)
|
||||
(add-transient-hook! 'hooks (setq value t))
|
||||
(run-hooks 'hooks)
|
||||
(expect value)
|
||||
(expect hooks :to-be nil)))
|
||||
(it "advises a function with a transient advisor"
|
||||
(let (value)
|
||||
(add-transient-hook! #'ignore (setq value (not value)))
|
||||
(ignore t)
|
||||
(expect value)
|
||||
;; repeat to ensure it was only run once
|
||||
(ignore t)
|
||||
(expect value))))
|
||||
|
||||
(describe "(un)setq-hook!"
|
||||
:var (fake-hook x y z)
|
||||
(before-each
|
||||
(setq x 10 y 20 z 30))
|
||||
|
||||
(it "sets variables buffer-locally"
|
||||
(setq-hook! 'fake-hook x 1)
|
||||
(with-temp-buffer
|
||||
(run-hooks 'fake-hook)
|
||||
(expect (local-variable-p 'x))
|
||||
(expect (= x 1)))
|
||||
(expect (= x 10)))
|
||||
|
||||
(it "overwrites earlier hooks"
|
||||
(setq-hook! 'fake-hook x 1 y 0)
|
||||
(setq-hook! 'fake-hook x 5 y -1)
|
||||
(with-temp-buffer
|
||||
(run-hooks 'fake-hook)
|
||||
(expect (= x 5))
|
||||
(expect (= y -1))))
|
||||
|
||||
(it "unset setq hooks"
|
||||
(setq-hook! 'fake-hook x 1 y 0)
|
||||
(unsetq-hook! 'fake-hook y)
|
||||
(with-temp-buffer
|
||||
(run-hooks 'fake-hook)
|
||||
(expect (local-variable-p 'x))
|
||||
(expect (= x 1))
|
||||
(expect (not (local-variable-p 'y)))
|
||||
(expect (= y 20))))))
|
||||
|
||||
(describe "load!"
|
||||
(before-each
|
||||
(spy-on 'load :and-return-value t))
|
||||
|
||||
(it "loads a file relative to the current directory"
|
||||
(load! "path")
|
||||
(expect 'load :to-have-been-called)
|
||||
(expect 'load :to-have-been-called-with
|
||||
(expand-file-name "path" (eval-when-compile (dir!))) nil 'nomessage))
|
||||
|
||||
(it "loads a file relative to a specified directory"
|
||||
(load! "path" doom-etc-dir)
|
||||
(expect 'load :to-have-been-called-with
|
||||
(expand-file-name "path" doom-etc-dir) nil 'nomessage)))
|
||||
|
||||
(describe "quiet!"
|
||||
:var (doom-debug-mode)
|
||||
(before-each
|
||||
(setq doom-debug-mode nil))
|
||||
|
||||
(it "suppresses output from message"
|
||||
(expect (message "hello world") :to-output "hello world\n")
|
||||
(expect (message "hello world") :to-output)
|
||||
(let (doom-interactive-mode)
|
||||
(expect (quiet! (message "hello world")) :not :to-output))
|
||||
(let ((doom-interactive-mode t))
|
||||
(expect (quiet! inhibit-message))
|
||||
(expect (quiet! save-silently))))
|
||||
|
||||
(it "suppresses load messages from `load' & `load-file'"
|
||||
(let ((tmpfile (make-temp-file "test" nil ".el")))
|
||||
(with-temp-file tmpfile)
|
||||
(let (doom-interactive-mode)
|
||||
(expect (load-file tmpfile) :to-output (format "Loading %s (source)...\n" tmpfile))
|
||||
(expect (quiet! (load-file tmpfile)) :not :to-output))
|
||||
(delete-file tmpfile)))
|
||||
|
||||
(it "won't suppress output in debug mode"
|
||||
(let ((doom-debug-mode t)
|
||||
(tmpfile (make-temp-file "test" nil ".el")))
|
||||
(dolist (doom-interactive-mode (list t nil))
|
||||
(expect (quiet! (message "hello world"))
|
||||
:to-output "hello world\n")
|
||||
(with-temp-file tmpfile)
|
||||
(expect (quiet! (load-file tmpfile))
|
||||
:to-output (format "Loading %s (source)...\n" tmpfile)))))))
|
|
@ -0,0 +1,23 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core-modules.el
|
||||
|
||||
(xdescribe "core-modules"
|
||||
(require 'core-modules)
|
||||
|
||||
(describe "doom!")
|
||||
(describe "doom-modules")
|
||||
|
||||
(describe "doom-module-p")
|
||||
(describe "doom-module-get")
|
||||
(describe "doom-module-put")
|
||||
(describe "doom-module-set")
|
||||
(describe "doom-module-path")
|
||||
(describe "doom-module-locate-path")
|
||||
(describe "doom-module-from-path")
|
||||
(describe "doom-module-load-path")
|
||||
|
||||
(describe "require!")
|
||||
(describe "featurep!")
|
||||
(describe "after!")
|
||||
(describe "use-package!")
|
||||
(describe "use-package-hook!"))
|
|
@ -0,0 +1,5 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core-packages.el
|
||||
;;;###if nil
|
||||
|
||||
(xdescribe "core-packages")
|
|
@ -0,0 +1,40 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core-projects.el
|
||||
|
||||
(describe "core/projects"
|
||||
:var (projectile-enable-caching)
|
||||
|
||||
(require 'core-projects)
|
||||
(require 'projectile)
|
||||
|
||||
(before-each
|
||||
(setq projectile-enable-caching nil)
|
||||
(projectile-mode +1))
|
||||
(after-each
|
||||
(projectile-mode -1))
|
||||
|
||||
(describe "project-p"
|
||||
(it "Should detect when in a valid project"
|
||||
(expect (doom-project-p doom-emacs-dir)))
|
||||
(it "Should detect when not in a valid project"
|
||||
(expect (doom-project-p (expand-file-name "~")) :to-be nil)))
|
||||
|
||||
(describe "project-root"
|
||||
(it "should resolve to the project's root"
|
||||
(expect (doom-project-root doom-core-dir) :to-equal-file doom-emacs-dir))
|
||||
(it "should return nil if not in a project"
|
||||
(expect (doom-project-root (expand-file-name "~")) :to-be nil)))
|
||||
|
||||
(describe "project-expand"
|
||||
(it "expands to a path relative to the project root"
|
||||
(expect (doom-project-expand "init.el" doom-core-dir) :to-equal-file
|
||||
(expand-file-name "init.el" (doom-project-root doom-core-dir)))))
|
||||
|
||||
(describe "project-file-exists-p!"
|
||||
(let ((default-directory doom-core-dir))
|
||||
;; Resolve from project root
|
||||
(expect (project-file-exists-p! "init.el"))
|
||||
;; Chained file checks
|
||||
(expect (project-file-exists-p! (and "init.el" "LICENSE")))
|
||||
(expect (project-file-exists-p! (or "init.el" "does-not-exist")))
|
||||
(expect (project-file-exists-p! (and "init.el" (or "LICENSE" "does-not-exist")))))))
|
|
@ -0,0 +1,106 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; ../core/test/test-core-ui.el
|
||||
|
||||
(describe "core/ui"
|
||||
(before-all
|
||||
(with-demoted-errors "Import error: %s"
|
||||
(require 'core-ui)))
|
||||
|
||||
(describe "doom-protect-fallback-buffer-h"
|
||||
:var (kill-buffer-query-functions)
|
||||
(before-all
|
||||
(setq kill-buffer-query-functions '(doom-protect-fallback-buffer-h)))
|
||||
|
||||
(it "should kill other buffers"
|
||||
(expect (kill-buffer (get-buffer-create "a"))))
|
||||
|
||||
(it "shouldn't kill the fallback buffer"
|
||||
(expect (not (kill-buffer (doom-fallback-buffer))))))
|
||||
|
||||
(describe "custom hooks"
|
||||
(describe "switch hooks"
|
||||
:var (before-hook after-hook a b)
|
||||
(before-each
|
||||
(setq a (switch-to-buffer (get-buffer-create "a"))
|
||||
b (get-buffer-create "b"))
|
||||
(spy-on 'hook)
|
||||
(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-buffer display-buffer))
|
||||
(advice-add fn :around #'doom-run-switch-buffer-hooks-a)))
|
||||
(after-each
|
||||
(remove-hook 'buffer-list-update-hook #'doom-run-switch-window-hooks-h)
|
||||
(remove-hook 'focus-in-hook #'doom-run-switch-frame-hooks-h)
|
||||
(dolist (fn '(switch-to-buffer display-buffer))
|
||||
(advice-remove fn #'doom-run-switch-buffer-hooks-a))
|
||||
(kill-buffer a)
|
||||
(kill-buffer b))
|
||||
|
||||
(describe "switch-buffer"
|
||||
:var (doom-switch-buffer-hook)
|
||||
(before-each
|
||||
(setq doom-switch-buffer-hook '(hook)))
|
||||
(after-each
|
||||
(setq doom-switch-buffer-hook nil))
|
||||
|
||||
(it "should trigger when switching buffers"
|
||||
(switch-to-buffer b)
|
||||
(switch-to-buffer a)
|
||||
(switch-to-buffer b)
|
||||
(expect 'hook :to-have-been-called-times 3))
|
||||
|
||||
(it "should trigger only once on the same buffer"
|
||||
(switch-to-buffer b)
|
||||
(switch-to-buffer b)
|
||||
(switch-to-buffer a)
|
||||
(expect 'hook :to-have-been-called-times 2)))
|
||||
|
||||
|
||||
(describe "switch-window"
|
||||
:var (doom-switch-window-hook x y)
|
||||
(before-each
|
||||
(delete-other-windows)
|
||||
(setq x (get-buffer-window a)
|
||||
y (save-selected-window (split-window)))
|
||||
(with-selected-window y
|
||||
(switch-to-buffer b))
|
||||
(select-window x)
|
||||
(spy-calls-reset 'hook)
|
||||
(setq doom-switch-window-hook '(hook)))
|
||||
|
||||
(it "should trigger when switching windows"
|
||||
(select-window y)
|
||||
(select-window x)
|
||||
(select-window y)
|
||||
(expect 'hook :to-have-been-called-times 3))
|
||||
|
||||
(it "should trigger only once on the same window"
|
||||
(select-window y)
|
||||
(select-window y)
|
||||
(select-window x)
|
||||
(expect 'hook :to-have-been-called-times 2)))
|
||||
|
||||
|
||||
(xdescribe "switch-frame"
|
||||
:var (doom-switch-frame-hook x y)
|
||||
(before-each
|
||||
(delete-other-windows)
|
||||
(setq x (get-buffer-window a)
|
||||
y (save-selected-window (split-window)))
|
||||
(with-selected-window y
|
||||
(switch-to-buffer b))
|
||||
(select-window x)
|
||||
(spy-calls-reset 'hook)
|
||||
(setq doom-switch-window-hook '(hook)))
|
||||
|
||||
(it "should trigger when switching windows"
|
||||
(select-window y)
|
||||
(select-window x)
|
||||
(select-window y)
|
||||
(expect 'hook :to-have-been-called-times 3))
|
||||
|
||||
(it "should trigger only once on the same window"
|
||||
(select-window y)
|
||||
(select-window y)
|
||||
(select-window x)
|
||||
(expect 'hook :to-have-been-called-times 2))))))
|
|
@ -0,0 +1,127 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core.el
|
||||
|
||||
(describe "core"
|
||||
:var (doom-interactive-p)
|
||||
(before-each
|
||||
(setq doom-interactive-p nil))
|
||||
|
||||
(describe "initialization"
|
||||
(describe "doom-initialize"
|
||||
:var (doom-init-p)
|
||||
(before-each
|
||||
(setq doom-init-p nil))
|
||||
|
||||
(it "initializes once"
|
||||
(expect (doom-initialize nil 'noerror))
|
||||
(expect (not (doom-initialize nil 'noerror)))
|
||||
(expect (not (doom-initialize nil 'noerror)))
|
||||
(expect doom-init-p))
|
||||
|
||||
(it "initializes multiple times, if forced"
|
||||
(expect (doom-initialize nil 'noerror))
|
||||
(expect (not (doom-initialize nil 'noerror)))
|
||||
(expect (doom-initialize 'force 'noerror)))
|
||||
|
||||
(describe "package initialization"
|
||||
(before-each
|
||||
(spy-on 'doom-initialize-packages :and-return-value t))
|
||||
|
||||
(it "initializes packages if core autoload file doesn't exist"
|
||||
(let ((doom-autoloads-file "doesnotexist"))
|
||||
(expect (doom-initialize nil 'noerror))
|
||||
(expect 'doom-initialize-packages :to-have-been-called))
|
||||
|
||||
(it "doesn't initialize packages if core autoload file was loaded"
|
||||
(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)
|
||||
(expect 'doom-initialize-packages :to-have-been-called)))
|
||||
|
||||
(it "initializes packages when forced"
|
||||
(doom-initialize 'force 'noerror)
|
||||
(expect 'doom-initialize-packages :to-have-been-called)))
|
||||
|
||||
(describe "autoloads files"
|
||||
(before-each
|
||||
(spy-on 'doom-load-autoloads-file)
|
||||
(spy-on 'warn :and-return-value t))
|
||||
|
||||
(it "loads autoloads files"
|
||||
(ignore-errors (doom-initialize nil 'noerror))
|
||||
(expect 'doom-load-autoloads-file
|
||||
:to-have-been-called-with doom-autoloads-file)
|
||||
(expect 'doom-load-autoloads-file
|
||||
:to-have-been-called-with doom-package-autoload-file))
|
||||
|
||||
(it "throws doom-autoload-error when autoload files don't exist"
|
||||
(let ((doom-autoloads-file "doesnotexist")
|
||||
(doom-package-autoload-file "doesnotexist"))
|
||||
(expect (doom-initialize) :to-throw 'doom-autoload-error)))))
|
||||
|
||||
(describe "doom-initialize-core"
|
||||
(before-each
|
||||
(spy-on 'require))
|
||||
|
||||
(it "loads all doom core libraries"
|
||||
(doom-initialize-core)
|
||||
(expect 'require :to-have-been-called-with 'core-keybinds)
|
||||
(expect 'require :to-have-been-called-with 'core-ui)
|
||||
(expect 'require :to-have-been-called-with 'core-projects)
|
||||
(expect 'require :to-have-been-called-with 'core-editor))))
|
||||
|
||||
(describe "doom-load-autoloads-file"
|
||||
:var (doom-autoloads-file doom-alt-autoload-file result)
|
||||
(before-each
|
||||
(setq doom-autoloads-file (make-temp-file "doom-autoload" nil ".el"))
|
||||
(with-temp-file doom-autoloads-file)
|
||||
(byte-compile-file doom-autoloads-file))
|
||||
(after-each
|
||||
(delete-file doom-autoloads-file)
|
||||
(delete-file (byte-compile-dest-file doom-autoloads-file)))
|
||||
|
||||
(it "loads the byte-compiled autoloads file if available"
|
||||
(doom-load-autoloads-file doom-autoloads-file)
|
||||
(expect (caar load-history) :to-equal-file
|
||||
(byte-compile-dest-file doom-autoloads-file))
|
||||
|
||||
(delete-file (byte-compile-dest-file doom-autoloads-file))
|
||||
(doom-load-autoloads-file doom-autoloads-file)
|
||||
(expect (caar load-history) :to-equal-file doom-autoloads-file))
|
||||
|
||||
(it "returns non-nil if successful"
|
||||
(expect (doom-load-autoloads-file doom-autoloads-file)))
|
||||
|
||||
(it "returns nil on failure or error, non-fatally"
|
||||
(expect (doom-load-autoloads-file "/does/not/exist") :to-be nil)))
|
||||
|
||||
(describe "doom-load-envvars-file"
|
||||
:var (doom-env-file process-environment)
|
||||
(before-each
|
||||
(setq process-environment nil
|
||||
doom-env-file (make-temp-file "doom-env"))
|
||||
(with-temp-file doom-env-file
|
||||
(insert "A=1\nB=2\nC=3\n")))
|
||||
(after-each
|
||||
(delete-file doom-env-file))
|
||||
|
||||
(it "throws a file-error if file doesn't exist"
|
||||
(expect (doom-load-envvars-file "/tmp/envvardoesnotexist")
|
||||
:to-throw 'file-error))
|
||||
|
||||
(it "to fail silently if NOERROR is non-nil"
|
||||
(expect (doom-load-envvars-file "/tmp/envvardoesnotexist" 'noerror)
|
||||
:not :to-throw))
|
||||
|
||||
(it "returns the new value for `process-environment'"
|
||||
(expect (doom-load-envvars-file doom-env-file)
|
||||
:to-have-same-items-as '("A" "B" "C")))
|
||||
|
||||
(it "alters environment variables"
|
||||
(dolist (key '("A" "B" "C"))
|
||||
(expect (getenv key) :not :to-be-truthy))
|
||||
(expect (doom-load-envvars-file doom-env-file))
|
||||
(expect (getenv "A") :to-equal "1")
|
||||
(expect (getenv "B") :to-equal "2")
|
||||
(expect (getenv "C") :to-equal "3"))))
|
|
@ -0,0 +1,167 @@
|
|||
#+TITLE: Contributing
|
||||
#+STARTUP: nofold
|
||||
|
||||
Doom Emacs is an active and ongoing project, maintained mostly by a single
|
||||
person, but includes the efforts of 200 contributors and growing. There is no
|
||||
shortage of things that need doing; bugs that need stomping, features that need
|
||||
implementing, and documentation that needs documenting. If Doom's been useful to
|
||||
you, convert some caffiene into code; it'd be a huge help!
|
||||
|
||||
You are welcome to [[https://discord.gg/qvGgnVx][join us on our Discord server]], otherwise read on to learn how
|
||||
to contribute to our fine corner of the interwebs.
|
||||
|
||||
* Table of Contents :TOC_3:
|
||||
- [[#where-can-i-help][Where can I help?]]
|
||||
- [[#reporting-issues][Reporting issues]]
|
||||
- [[#acquire-a-backtrace-from-errors][Acquire a backtrace from errors]]
|
||||
- [[#create-a-step-by-step-reproduction-guide][Create a step-by-step reproduction guide]]
|
||||
- [[#include-information-about-your-doom-install][Include information about your Doom install]]
|
||||
- [[#debugging-crashes-with-gdb][Debugging crashes with gdb]]
|
||||
- [[#suggesting-features-keybinds-andor-enhancements][Suggesting features, keybinds and/or enhancements]]
|
||||
- [[#contributing-code][Contributing code]]
|
||||
- [[#conventions][Conventions]]
|
||||
- [[#code-style][Code style]]
|
||||
- [[#naming-conventions][Naming conventions]]
|
||||
- [[#commits--prs][Commits & PRs]]
|
||||
- [[#keybind-conventions][Keybind conventions]]
|
||||
- [[#your-first-code-contribution][Your first code contribution]]
|
||||
- [[#submitting-pull-requests][Submitting pull requests]]
|
||||
- [[#contributing-to-doom-core][Contributing to Doom core]]
|
||||
- [[#contributing-to-an-existing-module][Contributing to an existing module]]
|
||||
- [[#contributing-a-new-module][Contributing a new module]]
|
||||
- [[#contributing-documentation][Contributing documentation]]
|
||||
- [[#contributing-to-dooms-manual][Contributing to Doom's manual]]
|
||||
- [[#contributing-module-documentation][Contributing module documentation]]
|
||||
- [[#help-keep-packages-up-to-date][Help keep packages up-to-date!]]
|
||||
- [[#other-ways-to-support-doom-emacs][Other ways to support Doom Emacs]]
|
||||
- [[#special-thanks][Special thanks]]
|
||||
|
||||
* Where can I help?
|
||||
+ Our [[https://github.com/hlissner/doom-emacs/issues][issue tracker]] has many issues. If you find one that you have an answer to,
|
||||
it would be a huge help!
|
||||
+ Look for issues tagged [[https://github.com/hlissner/doom-emacs/labels/good%20first%20issue][good first issue]]. These were judged to have a low
|
||||
barrier of entry.
|
||||
+ Look for issues tagged [[https://github.com/hlissner/doom-emacs/labels/help%20wanted][help wanted]]. These tend to be a little (or a lot)
|
||||
harder, and are issues outside my own expertise.
|
||||
+ If you've encountered a bug, [[https://github.com/hlissner/doom-emacs/issues/new/choose][file a bug report]].
|
||||
+ The [[https://github.com/hlissner/doom-emacs/projects/3][development roadmap board]] is a rough timeline of what is being worked on
|
||||
and when. It will give you an idea of what will change and where you can
|
||||
redirect your efforts.
|
||||
+ The [[https://github.com/hlissner/doom-emacs/projects/2][plugins under review board]] lists third party plugins being considered (or
|
||||
rejected) for inclusion in Doom Emacs. Approved and unclaimed packages are
|
||||
open for you to implement yourself.
|
||||
+ The [[https://github.com/hlissner/doom-emacs/projects/5][upstream bugs board]] lists known issues that have external causes, but
|
||||
affect Doom. If you're feeling adventurous (or are better acquainted with the
|
||||
cause) perhaps you can address them at the source.
|
||||
|
||||
* TODO Reporting issues
|
||||
You've found a problem and you're ready to fire off that bug report. Hold up!
|
||||
Before you do that, [[file:getting_started.org::*Troubleshoot][have a look at our Troubleshooting guide]]. If none of these
|
||||
suggestions pan out, /then/ it is time to file a bug report.
|
||||
|
||||
An effective bug report is informative. Please try to provide:
|
||||
|
||||
+ A backtrace of all mentioned errors.
|
||||
+ A step-by-step reproduction of the issue.
|
||||
+ Information about your Doom config and system environment.
|
||||
+ Screenshots/casts of the issue (if possible).
|
||||
|
||||
This section will show you how to collect this information.
|
||||
|
||||
** Acquire a backtrace from errors
|
||||
See "[[file:getting_started.org::*How to extract a backtrace from an error][How to extract a backtrace from an error]]" in the [[file:getting_started.org][Getting Started]] guide.
|
||||
|
||||
** TODO Create a step-by-step reproduction guide
|
||||
|
||||
** TODO Include information about your Doom install
|
||||
|
||||
** TODO Debugging crashes with gdb
|
||||
|
||||
* TODO Suggesting features, keybinds and/or enhancements
|
||||
|
||||
* TODO Contributing code
|
||||
There's much to be done around here! We need bugfixes, new features, and
|
||||
documentation. If you'd like to convert some caffeine into Emacs Lisp, here are
|
||||
a few considerations before starting that PR:
|
||||
|
||||
** TODO Conventions
|
||||
*** TODO Code style
|
||||
Doom conforms to [[https://github.com/bbatsov/emacs-lisp-style-guide][@bbatsov's emacs-lisp style guide]] with the following
|
||||
exceptions:
|
||||
|
||||
+ Use ~mapc~ instead of ~seq-do~.
|
||||
+ No hanging parentheses
|
||||
+ We use =DEPRECATED= to indicate code that will eventually be removed.
|
||||
|
||||
*** Naming conventions
|
||||
Doom has a number of naming conventions that it uses in addition to the standard
|
||||
lisp conventions. Third party packages may use their own conventions as well.
|
||||
|
||||
**** Lisp Naming Conventions
|
||||
The lisp conventions are simple. Symbols follow ~NAMESPACE-SYMBOLNAME~ for
|
||||
public variables/functions (e.g. ~bookmark-default-file~ or
|
||||
~electric-indent-mode~) and ~NAMESPACE--SYMBOLNAME~ for private ones (e.g.
|
||||
~byte-compile--lexical-environment~ and ~yas--tables~).
|
||||
|
||||
~NAMESPACE~ is usually the name of the containing file or package. E.g. the
|
||||
~company~ plugin prefixes all its variables/functions with ~company-~.
|
||||
|
||||
**** Doom Naming Conventions
|
||||
+ ~doom/NAME~ or ~+MODULE/NAME~ :: Denotes a public command designed to be used
|
||||
interactively, via =M-x= or a keybinding. e.g. ~doom/info~, ~+popup/other~,
|
||||
~+ivy/rg~.
|
||||
+ ~doom:NAME~ :: A public evil operator, motion or command. e.g. ~+evil:align~,
|
||||
~+ivy:rg~.
|
||||
+ ~doom-[-]NAME-h~ or ~+MODULE-[-]NAME-h~ :: A non-interactive function meant to
|
||||
be used (exclusively) as a hook. e.g. ~+cc-fontify-constants-h~,
|
||||
~+flycheck-buffer-h~.
|
||||
+ ~doom-[-]NAME-a~ or ~+MODULE-[-]NAME-a~ :: Functions designed to be used as
|
||||
advice for other functions. e.g. ~doom-set-jump-a~,
|
||||
~doom--fix-broken-smie-modes-a~, ~+org--babel-lazy-load-library-a~
|
||||
+ ~doom-[-]NAME-fn~ or ~+MODULE-[-]NAME-fn~ :: Indicates an [[https://en.wikipedia.org/wiki/Strategy_pattern][strategy]] function. A
|
||||
good rule of thumb for what makes a strategy function is: is it
|
||||
interchangeable? Can it be replaced with another function with a matching
|
||||
signature? e.g. ~+lookup-dumb-jump-backend-fn~, ~+magit-display-buffer-fn~,
|
||||
~+workspaces-set-project-action-fn~
|
||||
+ ~abc!~ :: A public Doom "autodef" function or macro. An autodef should always
|
||||
be defined, even if its containing module is disabled (i.e. they will not
|
||||
throw a void-function error). The purpose of this is to avoid peppering module
|
||||
configs with conditionals or `after!` blocks before using their APIs. They
|
||||
should noop if their module is disabled, and should be zero-cost in the case
|
||||
their module is disabled.
|
||||
|
||||
Autodefs usually serve to configure Doom or a module. e.g. ~after!~,
|
||||
~set-company-backends!~, ~set-evil-initial-state!~
|
||||
|
||||
*** TODO Commits & PRs
|
||||
+ Target =develop= instead of =master=. The only exception are hotfixes!
|
||||
|
||||
*** TODO Keybind conventions
|
||||
|
||||
** TODO Your first code contribution
|
||||
|
||||
** TODO Submitting pull requests
|
||||
|
||||
** TODO Contributing to Doom core
|
||||
|
||||
** TODO Contributing to an existing module
|
||||
|
||||
** TODO Contributing a new module
|
||||
|
||||
* TODO Contributing documentation
|
||||
Doom Emacs' documentation is an ongoing effort. If you have suggestions,
|
||||
improvements, tutorials and/or articles to submit, don't hesitate to get in
|
||||
contact via our [[https://discord.gg/bcZ6P3y][Discord server]] or [[mailto:henrik@lissner.net][email]]. I appreciate any help I can get!
|
||||
|
||||
** TODO Contributing to Doom's manual
|
||||
|
||||
** TODO Contributing module documentation
|
||||
|
||||
* TODO Help keep packages up-to-date!
|
||||
Doom pins all its packages to reduce the likelihood of upstream breakage leaking
|
||||
into Doom Emacs. However, we may miss when a package releases hotfixes for
|
||||
critical issues. Let us know or PR a bump to our pinned packages.
|
||||
|
||||
* TODO Other ways to support Doom Emacs
|
||||
|
||||
* TODO Special thanks
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,106 @@
|
|||
#+TITLE: Doom Emacs Documentation
|
||||
#+STARTUP: nofold
|
||||
|
||||
Doom is a configuration framework for [[https://www.gnu.org/software/emacs/][GNU Emacs 26.3+]] tailored for Emacs
|
||||
bankruptcy veterans who want less framework in their frameworks and the
|
||||
performance of a hand rolled config (or better). It can be a foundation for your
|
||||
own config or a resource for Emacs enthusiasts to learn more about our favorite
|
||||
OS.
|
||||
|
||||
Doom is an opinionated collection of reasonable (and optional) defaults with a
|
||||
focus on performance (both runtime and startup) and on abstraction-light,
|
||||
readable code design, so that there is less between you and Emacs.
|
||||
|
||||
#+begin_quote
|
||||
The documentation is designed to be viewed within Doom Emacs. Access it by
|
||||
pressing =SPC h d h= (or =C-h d h= for non-evil users), or search it with =SPC h
|
||||
d s= (or =C-h d s=).
|
||||
#+end_quote
|
||||
|
||||
* Table of Contents :TOC:
|
||||
- [[#release-notes][Release Notes]]
|
||||
- [[#documentation][Documentation]]
|
||||
- [[#getting-started][Getting Started]]
|
||||
- [[#frequently-asked-questions][Frequently Asked Questions]]
|
||||
- [[#contributing][Contributing]]
|
||||
- [[#workflow-tips-tricks--tutorials][Workflow Tips, Tricks & Tutorials]]
|
||||
- [[#module-appendix][Module Appendix]]
|
||||
- [[#community-resources][Community Resources]]
|
||||
- [[#asking-for-help][Asking for help]]
|
||||
- [[#project-roadmap][Project roadmap]]
|
||||
- [[#tutorials--guides][Tutorials & guides]]
|
||||
- [[#projects-that-supportcomplement-doom][Projects that support/complement Doom]]
|
||||
- [[#similar-projects][Similar projects]]
|
||||
|
||||
* TODO Release Notes
|
||||
|
||||
* Documentation
|
||||
** [[file:getting_started.org][Getting Started]]
|
||||
- [[file:getting_started.org::*Install][Install]]
|
||||
- [[file:getting_started.org::*Update & Rollback][Update & Rollback]]
|
||||
- [[file:getting_started.org::*Configure][Configure]]
|
||||
- [[file:getting_started.org::*Migrate][Migrate]]
|
||||
- [[file:getting_started.org::*Troubleshoot][Troubleshoot]]
|
||||
|
||||
** [[file:faq.org][Frequently Asked Questions]]
|
||||
- [[file:faq.org::*General][General]]
|
||||
- [[file:faq.org::*Configuration][Configuration]]
|
||||
- [[file:faq.org::*Package Management][Package Management]]
|
||||
- [[file:faq.org::*Defaults][Defaults]]
|
||||
- [[file:faq.org::Common Issues][Common Issues]]
|
||||
- [[file:faq.org::Contributing][Contributing]]
|
||||
|
||||
** TODO [[file:contributing.org][Contributing]]
|
||||
- [[file:contributing.org::*Where can I help?][Where to get help?]]
|
||||
- Reporting issues
|
||||
- Suggesting features, keybinds and enhancements
|
||||
- Contributing code or documentation
|
||||
- Other ways to support Doom Emacs
|
||||
- Special thanks
|
||||
|
||||
** TODO [[file:workflow.org][Workflow Tips, Tricks & Tutorials]]
|
||||
|
||||
** [[file:modules.org][Module Appendix]]
|
||||
|
||||
* Community Resources
|
||||
** Asking for help
|
||||
- [[https://discord.gg/qvGgnVx][Our Discord server]]
|
||||
- [[https://github.com/hlissner/doom-emacs/issues][Our issue tracker]]
|
||||
|
||||
** Project roadmap
|
||||
- [[https://github.com/hlissner/doom-emacs/projects/3][Development roadmap]] - A timeline outlining what's being worked on and when it
|
||||
is expected to be done.
|
||||
- [[https://github.com/hlissner/doom-emacs/projects/2][Plugins under review]] - A sitrep on third party plugins that we've considered,
|
||||
rejected, or awaiting integration into Doom.
|
||||
- [[https://github.com/hlissner/doom-emacs/projects/5][Upstream bugs]] - Tracks issues originating from plugins and external programs
|
||||
that Doom relies on.
|
||||
|
||||
** Tutorials & guides
|
||||
+ *Doom Emacs*
|
||||
- (videos) [[https://www.youtube.com/playlist?list=PLyy8KUDC8P7X6YkegqrnEnymzMWCNB4bN][Doom Emacs Tutorials]] by [[https://www.youtube.com/channel/UCVls1GmFKf6WlTraIb_IaJg][DistroTube]]
|
||||
- (videos) [[https://www.youtube.com/playlist?list=PLhXZp00uXBk4np17N39WvB80zgxlZfVwj][DoomCasts]] by @zaiste
|
||||
- [[https://noelwelsh.com/posts/2019-01-10-doom-emacs.html][Noel's crash course on Doom Emacs]]
|
||||
- [[https://medium.com/@aria_39488/getting-started-with-doom-emacs-a-great-transition-from-vim-to-emacs-9bab8e0d8458][Getting Started with Doom Emacs -- a great transition from Vim to Emacs]]
|
||||
- [[https://medium.com/@aria_39488/the-niceties-of-evil-in-doom-emacs-cabb46a9446b][The Niceties of evil in Doom Emacs]]
|
||||
- (video) [[https://www.youtube.com/watch?v=GK3fij-D1G8][Org-mode, literate programming in (Doom) Emacs]]
|
||||
+ *Emacs & Emacs Lisp*
|
||||
- [[https://www.gnu.org/software/emacs/manual/html_node/elisp/index.html][The Official Emacs manual]]
|
||||
- A variety of Emacs resources - https://github.com/ema2159/awesome-emacs
|
||||
- Quick crash courses on Emacs Lisp's syntax for programmers:
|
||||
- https://learnxinyminutes.com/docs/elisp/
|
||||
- http://steve-yegge.blogspot.com/2008/01/emergency-elisp.html
|
||||
- Workflows for customizing Emacs and its packages (and its C/C++ modes):
|
||||
- https://david.rothlis.net/emacs/customize_c.html
|
||||
- *Tools in Emacs*
|
||||
- [[https://www.emacswiki.org/emacs/Calc_Tutorials_by_Andrew_Hyatt][How to use M-x calc]]
|
||||
+ *Vim & Evil*
|
||||
- [[https://gist.github.com/dmsul/8bb08c686b70d5a68da0e2cb81cd857f][A crash course on modal editing and Ex commands]]
|
||||
|
||||
** Projects that support/complement Doom
|
||||
+ [[https://github.com/plexus/chemacs][plexus/chemacs]]
|
||||
+ [[https://github.com/r-darwish/topgrade][r-darwish/topgrade]]
|
||||
|
||||
** Similar projects
|
||||
+ [[https://github.com/purcell/emacs.d][purcell/emacs.d]]
|
||||
+ [[https://github.com/seagle0128/.emacs.d][seagle0128/.emacs.d]]
|
||||
+ [[https://github.com/syl20bnr/spacemacs][syl20bnr/spacemacs]]
|
|
@ -0,0 +1,219 @@
|
|||
#+TITLE: Module Appendix
|
||||
#+STARTUP: nofold
|
||||
|
||||
Functionality in Doom is divided into collections of code called modules (à la
|
||||
Spacemacs' layers). A module is a bundle of packages, configuration and
|
||||
commands, organized into a unit that can be enabled or disabled by adding or
|
||||
removing them from your ~doom!~ block in =$DOOMDIR/init.el=.
|
||||
|
||||
* Table of Contents :TOC:
|
||||
- [[#app][:app]]
|
||||
- [[#checkers][:checkers]]
|
||||
- [[#completion][:completion]]
|
||||
- [[#config][:config]]
|
||||
- [[#editor][:editor]]
|
||||
- [[#emacs][:emacs]]
|
||||
- [[#email][:email]]
|
||||
- [[#input][:input]]
|
||||
- [[#lang][:lang]]
|
||||
- [[#os][:os]]
|
||||
- [[#term][:term]]
|
||||
- [[#tools][:tools]]
|
||||
- [[#ui][:ui]]
|
||||
|
||||
* :app
|
||||
Application modules are complex and opinionated modules that transform Emacs
|
||||
toward a specific purpose. They may have additional dependencies and *should be
|
||||
loaded last*, before =:config= modules.
|
||||
|
||||
+ [[file:../modules/app/calendar/README.org][calendar]] - TODO
|
||||
+ [[file:../modules/app/irc/README.org][irc]] - how neckbeards socialize
|
||||
+ rss =+org= - an RSS client in Emacs
|
||||
+ [[file:../modules/app/twitter/README.org][twitter]] - A twitter client for Emacs
|
||||
|
||||
* :checkers
|
||||
+ syntax =+childframe= - Live error/warning highlights
|
||||
+ spell =+everywhere +aspell +hunspell +enchant= - Spell checking
|
||||
+ grammar - TODO
|
||||
|
||||
* :completion
|
||||
Modules that provide new interfaces or frameworks for completion, including code
|
||||
completion.
|
||||
|
||||
+ [[file:../modules/completion/company/README.org][company]] =+childframe +tng= - The ultimate code completion backend
|
||||
+ helm =+fuzzy +childframe= - *Another* search engine for love and life
|
||||
+ ido - The /other/ *other* search engine for love and life
|
||||
+ [[file:../modules/completion/ivy/README.org][ivy]] =+fuzzy +prescient +childframe +icons= - /The/ search engine for love and life
|
||||
|
||||
* :config
|
||||
Modules that configure Emacs one way or another, or focus on making it easier
|
||||
for you to customize it yourself. It is best to load these last.
|
||||
|
||||
+ literate - For users with literate configs. This will tangle+compile a
|
||||
config.org in your ~doom-private-dir~ when it changes.
|
||||
+ [[file:../modules/config/default/README.org][default]] =+bindings +smartparens= - The default module sets reasonable defaults
|
||||
for Emacs. It also provides a Spacemacs-inspired keybinding scheme and a
|
||||
smartparens config. Use it as a reference for your own modules.
|
||||
|
||||
* :editor
|
||||
Modules that affect and augment your ability to manipulate or insert text.
|
||||
|
||||
+ [[file:../modules/editor/evil/README.org][evil]] =+everywhere= - transforms Emacs into Vim
|
||||
+ [[file:../modules/editor/file-templates/README.org][file-templates]] - Auto-inserted templates in blank new files
|
||||
+ [[file:../modules/editor/fold/README.org][fold]] - universal code folding
|
||||
+ format =+onsave= - TODO
|
||||
+ god - TODO
|
||||
+ [[file:../modules/editor/lispy/README.org][lispy]] - TODO
|
||||
+ multiple-cursors - TODO
|
||||
+ [[file:../modules/editor/objed/README.org][objed]] =+manual= - TODO
|
||||
+ [[file:../modules/editor/parinfer/README.org][parinfer]] - TODO
|
||||
+ rotate-text - TODO
|
||||
+ [[file:../modules/editor/snippets/README.org][snippets]] - Snippet expansion for lazy typists
|
||||
+ [[file:../modules/editor/word-wrap/README.org][word-wrap]] - soft wrapping with language-aware indent
|
||||
|
||||
* :emacs
|
||||
Modules that reconfigure or augment packages or features built into Emacs.
|
||||
|
||||
+ [[file:../modules/emacs/dired/README.org][dired]] =+ranger +icons= - TODO
|
||||
+ electric - TODO
|
||||
+ [[file:../modules/emacs/ibuffer/README.org][ibuffer]] =+icons= - TODO
|
||||
+ [[file:../modules/emacs/undo/README.org][undo]] =+tree= - A smarter, more intuitive & persistent undo history
|
||||
+ vc - TODO
|
||||
|
||||
* :email
|
||||
+ [[file:../modules/email/mu4e/README.org][mu4e]] =+gmail= - TODO
|
||||
+ notmuch - TODO
|
||||
+ wanderlust =+gmail= - TODO
|
||||
|
||||
* :input
|
||||
+ [[file:../modules/input/chinese/README.org][chinese]] - TODO
|
||||
+ [[file:../modules/input/japanese/README.org][japanese]] - TODO
|
||||
|
||||
* :lang
|
||||
Modules that bring support for a language or group of languages to Emacs.
|
||||
|
||||
+ [[file:../modules/lang/agda/README.org][agda]] =+local= - TODO
|
||||
+ [[file:../modules/lang/cc/README.org][cc]] =+lsp= - TODO
|
||||
+ [[file:../modules/lang/clojure/README.org][clojure]] =+lsp= - TODO
|
||||
+ common-lisp - TODO
|
||||
+ [[file:../modules/lang/coq/README.org][coq]] - TODO
|
||||
+ crystal - TODO
|
||||
+ [[file:../modules/lang/csharp/README.org][csharp]] =+lsp +unity= - TODO
|
||||
+ [[file:../modules/lang/dart/README.org][dart]] =+lsp +flutter=
|
||||
+ data - TODO
|
||||
+ [[file:../modules/lang/elixir/README.org][elixir]] =+lsp= - TODO
|
||||
+ elm =+lsp= - TODO
|
||||
+ emacs-lisp - TODO
|
||||
+ erlang =+lsp= - TODO
|
||||
+ [[file:../modules/lang/ess/README.org][ess]] =+lsp= - TODO
|
||||
+ [[file:../modules/lang/factor/README.org][factor]] - TODO
|
||||
+ [[file:../modules/lang/faust/README.org][faust]] - TODO
|
||||
+ [[file:../modules/lang/fsharp/README.org][fsharp]] =+lsp= - TODO
|
||||
+ [[file:../modules/lang/fstar/README.org][fstar]] - F* support
|
||||
+ [[file:../modules/lang/gdscript/README.org][gdscript]] =+lsp= - TODO
|
||||
+ [[file:../modules/lang/go/README.org][go]] =+lsp= - TODO
|
||||
+ [[file:../modules/lang/haskell/README.org][haskell]] =+dante +ghcide +lsp= - TODO
|
||||
+ hy - TODO
|
||||
+ [[file:../modules/lang/idris/README.org][idris]] - TODO
|
||||
+ java =+meghanada +eclim +lsp= - TODO
|
||||
+ [[file:../modules/lang/javascript/README.org][javascript]] =+lsp= - JavaScript, TypeScript, and CoffeeScript support
|
||||
+ [[file:../modules/lang/json/README.org][json]] =+lsp= - TODO
|
||||
+ julia =+lsp= - TODO
|
||||
+ kotlin =+lsp+= - TODO
|
||||
+ [[file:../modules/lang/latex/README.org][latex]] =+latexmk +cdlatex +fold +lsp= - TODO
|
||||
+ lean - TODO
|
||||
+ [[file:../modules/lang/ledger/README.org][ledger]] - TODO
|
||||
+ lua =+fennel +moonscript= - TODO
|
||||
+ [[file:../modules/lang/markdown/README.org][markdown]] =+grip= - TODO
|
||||
+ [[file:../modules/lang/nim/README.org][nim]] - TODO
|
||||
+ nix - TODO
|
||||
+ [[file:../modules/lang/ocaml/README.org][ocaml]] =+lsp= - TODO
|
||||
+ [[file:../modules/lang/org/README.org][org]] =+brain +dragndrop +gnuplot +hugo +ipython +journal +jupyter +noter +pandoc +pomodoro +present +pretty +roam= - TODO
|
||||
+ [[file:../modules/lang/php/README.org][php]] =+hack +lsp= - TODO
|
||||
+ plantuml - TODO
|
||||
+ purescript =+lsp= - TODO
|
||||
+ [[file:../modules/lang/python/README.org][python]] =+cython +lsp +pyright +pyenv +conda +poetry= - TODO
|
||||
+ qt - TODO
|
||||
+ racket - TODO
|
||||
+ [[file:../modules/lang/raku/README.org][raku]] - TODO
|
||||
+ [[file:../modules/lang/rest/README.org][rest]] - TODO
|
||||
+ rst - TODO
|
||||
+ [[file:../modules/lang/ruby/README.org][ruby]] =+lsp +rvm +rbenv +rails +chruby=
|
||||
+ [[file:../modules/lang/rust/README.org][rust]] =+lsp= - TODO
|
||||
+ scala =+lsp= - TODO
|
||||
+ [[file:../modules/lang/scheme/README.org][scheme]] - TODO
|
||||
+ [[file:../modules/lang/sh/README.org][sh]] =+fish +lsp +powershell= - TODO
|
||||
+ [[file:../modules/lang/sml/README.org][sml]] - TODO
|
||||
+ [[file:../modules/lang/solidity/README.org][solidity]] - TODO
|
||||
+ swift =+lsp= - TODO
|
||||
+ terra - TODO
|
||||
+ web =+lsp= - HTML and CSS (SCSS/SASS/LESS/Stylus) support.
|
||||
+ [[file:../modules/lang/yaml/README.org][yaml]] =+lsp= - TODO
|
||||
|
||||
|
||||
* :os
|
||||
Modules to improve integration into your OS, system, or devices.
|
||||
|
||||
+ [[file:../modules/os/macos/README.org][macos]] - Improve Emacs' compatibility with macOS
|
||||
+ tty =+osc= - Improves the terminal Emacs experience.
|
||||
|
||||
* :term
|
||||
Modules that offer terminal emulation.
|
||||
|
||||
+ eshell - TODO
|
||||
+ shell - TODO
|
||||
+ term - TODO
|
||||
+ [[file:../modules/term/vterm/README.org][vterm]] - TODO
|
||||
|
||||
* :tools
|
||||
Small modules that give Emacs access to external tools & services.
|
||||
|
||||
+ ansible - TODO
|
||||
+ debugger =+lsp= - A (nigh-)universal debugger in Emacs
|
||||
+ [[file:../modules/tools/direnv/README.org][direnv]] - TODO
|
||||
+ [[file:../modules/tools/docker/README.org][docker]] =+lsp= - TODO
|
||||
+ [[file:../modules/tools/editorconfig/README.org][editorconfig]] - TODO
|
||||
+ [[file:../modules/tools/ein/README.org][ein]] - TODO
|
||||
+ [[file:../modules/tools/eval/README.org][eval]] =+overlay= - REPL & code evaluation support for a variety of languages
|
||||
+ gist - TODO
|
||||
+ [[file:../modules/tools/lookup/README.org][lookup]] =+dictionary +docsets +offline= - Universal jump-to & documentation lookup
|
||||
backend
|
||||
+ [[file:../modules/tools/lsp/README.org][lsp]] =+peek +eglot= - Installation and configuration of language server protocol client (lsp-mode or eglot)
|
||||
+ [[file:../modules/tools/magit/README.org][magit]] =+forge= - TODO
|
||||
+ make - TODO
|
||||
+ pass =+auth= - TODO
|
||||
+ pdf - TODO
|
||||
+ prodigy - TODO
|
||||
+ rgb - TODO
|
||||
+ [[file:../modules/tools/taskrunner/README.org][taskrunner]] - TODO
|
||||
+ [[file:../modules/tools/terraform/README.org][terraform]] - TODO
|
||||
+ tmux - TODO
|
||||
+ upload - TODO
|
||||
|
||||
* :ui
|
||||
Aesthetic modules that affect the Emacs interface or user experience.
|
||||
|
||||
+ [[file:../modules/ui/deft/README.org][deft]] - TODO
|
||||
+ [[file:../modules/ui/doom/README.org][doom]] - TODO
|
||||
+ [[file:../modules/ui/doom-dashboard/README.org][doom-dashboard]] - TODO
|
||||
+ [[file:../modules/ui/doom-quit/README.org][doom-quit]] - TODO
|
||||
+ [[file:../modules/ui/emoji/README.org][emoji]] =+ascii +github +unicode= - Adds emoji support to Emacs
|
||||
+ fill-column - TODO
|
||||
+ [[file:../modules/ui/hl-todo/README.org][hl-todo]] - TODO
|
||||
+ [[file:../modules/ui/hydra/README.org][hydra]] - TODO
|
||||
+ indent-guides - TODO
|
||||
+ [[file:../modules/ui/ligatures/README.org][ligatures]] =+extra +fira +hasklig +iosevka +pragmata-pro= - Ligature support for Emacs
|
||||
+ [[file:../modules/ui/minimap/README.org][minimap]] - TODO
|
||||
+ [[file:../modules/ui/modeline/README.org][modeline]] =+light= - TODO
|
||||
+ [[file:../modules/ui/nav-flash/README.org][nav-flash]] - TODO
|
||||
+ [[file:../modules/ui/neotree/README.org][neotree]] - TODO
|
||||
+ [[file:../modules/ui/ophints/README.org][ophints]] - TODO
|
||||
+ [[file:../modules/ui/popup/README.org][popup]] =+all +defaults= - Makes temporary/disposable windows less intrusive
|
||||
+ [[file:../modules/ui/tabs/README.org][tabs]] - TODO
|
||||
+ treemacs - TODO
|
||||
+ [[file:../modules/ui/unicode/README.org][unicode]] - TODO
|
||||
+ vc-gutter - TODO
|
||||
+ vi-tilde-fringe - TODO
|
||||
+ [[file:../modules/ui/window-select/README.org][window-select]] =+switch-window +numbers= - TODO
|
||||
+ [[file:../modules/ui/workspaces/README.org][workspaces]] - Isolated workspaces
|
||||
+ [[file:../modules/ui/zen/README.org][zen]] - Distraction-free coding (or writing)
|
|
@ -0,0 +1,89 @@
|
|||
#+TITLE: Getting to know Doom Emacs
|
||||
#+STARTUP: nofold
|
||||
|
||||
Once you've installed Doom and launched it, the next step of your masochistic
|
||||
journey is to master it. This guide will walk you through many Doom-centric
|
||||
workflows you'll commonly find in a text editor (and beyond). It isn't
|
||||
exhaustive because I don't have enough lives to make it so.
|
||||
|
||||
#+begin_quote
|
||||
If you feel like we've missed something, don't hesitate to let us know! You're
|
||||
welcome to [[https://discord.gg/qvGgnVx][join us on our Discord server]].
|
||||
#+end_quote
|
||||
|
||||
* Table of Contents :TOC:
|
||||
- [[#day-1-in-doom-emacs][Day 1 in Doom Emacs]]
|
||||
- [[#an-introduction-to-modal-editing-with-evil][An introduction to modal editing with Evil]]
|
||||
- [[#an-introduction-to-vim][An introduction to Vim]]
|
||||
- [[#getting-around-the-buffer][Getting around the buffer]]
|
||||
- [[#operating-with-operators][Operating with operators]]
|
||||
- [[#get-what-you-want-with-text-objects][Get what you want with text objects]]
|
||||
- [[#ways-to-edit-multiple-places-at-once][Ways to edit multiple places at once]]
|
||||
- [[#pipe-text-through-ex-commands-and-programs][Pipe text through ex commands and programs]]
|
||||
- [[#transposingswapping-text][Transposing/swapping text]]
|
||||
- [[#managing-your-projects][Managing your projects]]
|
||||
- [[#reconfiguring-emacs-on-a-per-project-basis][Reconfiguring Emacs on a per-project basis]]
|
||||
- [[#search--replace][Search & replace]]
|
||||
- [[#project-wide-text-search][Project-wide text search]]
|
||||
- [[#search--replace-1][Search & replace]]
|
||||
- [[#how-to-get-around-your-projects][How to get around your projects]]
|
||||
- [[#looking-up-definitionreferences][Looking up definition/references]]
|
||||
- [[#looking-up-documentation][Looking up documentation]]
|
||||
- [[#projectfile-navigation-getting-around-quickly][Project/file navigation; getting around quickly]]
|
||||
- [[#getting-around-slowly][Getting around slowly]]
|
||||
- [[#alternating-files-eg-scss-css-h-c][Alternating files (e.g. .scss<->.css, .h<->.c)]]
|
||||
- [[#looking-things-up-online-or-in-documentation][Looking things up online or in documentation]]
|
||||
- [[#creating-and-using-snippets][Creating and using snippets]]
|
||||
- [[#how-to-use-workspaces][How to use workspaces]]
|
||||
- [[#version-control-with-magit-forge--git-gutter][Version control with Magit, Forge & git-gutter]]
|
||||
- [[#how-to-write--debug-emacs-lisp][How to write & debug Emacs Lisp]]
|
||||
- [[#on-the-fly-code-evaluation--repls][On-the-fly code evaluation & REPLs]]
|
||||
- [[#using-emacs-for][Using Emacs for...]]
|
||||
- [[#writing-fiction][Writing fiction]]
|
||||
- [[#writing-papers][Writing papers]]
|
||||
- [[#note-keeping][Note-keeping]]
|
||||
- [[#a-personal-organizer][A Personal Organizer]]
|
||||
- [[#composing-music][Composing music]]
|
||||
- [[#game-development][Game development]]
|
||||
- [[#web-development][Web development]]
|
||||
- [[#machine-learning][Machine learning]]
|
||||
- [[#as-an-x-client-twitter-email-rss-reader-etc][As an X client (twitter, email, RSS reader, etc)]]
|
||||
|
||||
* TODO Day 1 in Doom Emacs
|
||||
* TODO An introduction to modal editing with Evil
|
||||
** TODO An introduction to Vim
|
||||
** TODO Getting around the buffer
|
||||
** TODO Operating with operators
|
||||
** TODO Get what you want with text objects
|
||||
** TODO Ways to edit multiple places at once
|
||||
** TODO Pipe text through ex commands and programs
|
||||
** TODO Transposing/swapping text
|
||||
* TODO Managing your projects
|
||||
** TODO Reconfiguring Emacs on a per-project basis
|
||||
*** TODO .dir-locals.el
|
||||
*** TODO editorconfig
|
||||
* TODO Search & replace
|
||||
** TODO Project-wide text search
|
||||
** TODO Search & replace
|
||||
* TODO How to get around your projects
|
||||
** TODO Looking up definition/references
|
||||
** TODO Looking up documentation
|
||||
** TODO Project/file navigation; getting around quickly
|
||||
** TODO Getting around slowly
|
||||
** TODO Alternating files (e.g. .scss<->.css, .h<->.c)
|
||||
* TODO Looking things up online or in documentation
|
||||
* TODO Creating and using snippets
|
||||
* TODO How to use workspaces
|
||||
* TODO Version control with Magit, Forge & git-gutter
|
||||
* TODO How to write & debug Emacs Lisp
|
||||
* TODO On-the-fly code evaluation & REPLs
|
||||
* TODO Using Emacs for...
|
||||
** TODO Writing fiction
|
||||
** TODO Writing papers
|
||||
** TODO Note-keeping
|
||||
** TODO A Personal Organizer
|
||||
** TODO Composing music
|
||||
** TODO Game development
|
||||
** TODO Web development
|
||||
** TODO Machine learning
|
||||
** TODO As an X client (twitter, email, RSS reader, etc)
|
|
@ -0,0 +1,50 @@
|
|||
#+TITLE: app/calendar
|
||||
#+DATE: January 13, 2018
|
||||
#+SINCE: v2.1
|
||||
#+STARTUP: inlineimages
|
||||
|
||||
* Table of Contents :TOC:
|
||||
- [[#description][Description]]
|
||||
- [[#module-flags][Module Flags]]
|
||||
- [[#packages][Packages]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#changing-calendar-sources][Changing calendar sources]]
|
||||
- [[#synchronizing-org-and-google-calendar][Synchronizing Org and Google Calendar]]
|
||||
|
||||
* Description
|
||||
This module adds a calendar view for Emacs, with org and google calendar sync
|
||||
support.
|
||||
|
||||
** Module Flags
|
||||
This module provides no flags.
|
||||
|
||||
** Packages
|
||||
+ [[https://github.com/kiwanami/emacs-calfw][calfw]]
|
||||
+ [[https://github.com/kiwanami/emacs-calfw][calfw-org]]
|
||||
+ [[https://github.com/kidd/org-gcal.el][org-gcal]]
|
||||
|
||||
* Configuration
|
||||
** Changing calendar sources
|
||||
By defining your own calendar commands, you can control what sources to pull
|
||||
calendar data from:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun my-open-calendar ()
|
||||
(interactive)
|
||||
(cfw:open-calendar-buffer
|
||||
:contents-sources
|
||||
(list
|
||||
(cfw:org-create-source "Green") ; org-agenda source
|
||||
(cfw:org-create-file-source "cal" "/path/to/cal.org" "Cyan") ; other org source
|
||||
(cfw:howm-create-source "Blue") ; howm source
|
||||
(cfw:cal-create-source "Orange") ; diary source
|
||||
(cfw:ical-create-source "Moon" "~/moon.ics" "Gray") ; ICS source1
|
||||
(cfw:ical-create-source "gcal" "https://..../basic.ics" "IndianRed") ; google calendar ICS
|
||||
)))
|
||||
#+END_SRC
|
||||
|
||||
The [[https://github.com/kiwanami/emacs-calfw][kiwanami/emacs-calfw]] project readme contains more examples.
|
||||
|
||||
** Synchronizing Org and Google Calendar
|
||||
The [[https://github.com/kidd/org-gcal.el][kidd/org-gcal.el]] project README contains more detailed instructions on how
|
||||
to link your calendar with Google calendars.
|
|
@ -0,0 +1,62 @@
|
|||
;;; app/calendar/autoload.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +calendar--wconf nil)
|
||||
|
||||
(defun +calendar--init ()
|
||||
(if-let (win (cl-find-if (lambda (b) (string-match-p "^\\*cfw:" (buffer-name b)))
|
||||
(doom-visible-windows)
|
||||
:key #'window-buffer))
|
||||
(select-window win)
|
||||
(call-interactively +calendar-open-function)))
|
||||
|
||||
;;;###autoload
|
||||
(defun =calendar ()
|
||||
"Activate (or switch to) `calendar' in its workspace."
|
||||
(interactive)
|
||||
(if (featurep! :ui workspaces)
|
||||
(progn
|
||||
(+workspace-switch "Calendar" t)
|
||||
(doom/switch-to-scratch-buffer)
|
||||
(+calendar--init)
|
||||
(+workspace/display))
|
||||
(setq +calendar--wconf (current-window-configuration))
|
||||
(delete-other-windows)
|
||||
(switch-to-buffer (doom-fallback-buffer))
|
||||
(+calendar--init)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +calendar/quit ()
|
||||
"TODO"
|
||||
(interactive)
|
||||
(if (featurep! :ui workspaces)
|
||||
(+workspace/delete "Calendar")
|
||||
(doom-kill-matching-buffers "^\\*cfw:")
|
||||
(when (window-configuration-p +calendar--wconf)
|
||||
(set-window-configuration +calendar--wconf))
|
||||
(setq +calendar--wconf nil)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +calendar/open-calendar ()
|
||||
"TODO"
|
||||
(interactive)
|
||||
(cfw:open-calendar-buffer
|
||||
;; :custom-map cfw:my-cal-map
|
||||
:contents-sources
|
||||
(list
|
||||
(cfw:org-create-source (face-foreground 'default)) ; orgmode source
|
||||
)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +calendar-cfw:render-button-a (title command &optional state)
|
||||
"render-button
|
||||
TITLE
|
||||
COMMAND
|
||||
STATE"
|
||||
(let ((text (concat " " title " "))
|
||||
(keymap (make-sparse-keymap)))
|
||||
(cfw:rt text (if state 'cfw:face-toolbar-button-on
|
||||
'cfw:face-toolbar-button-off))
|
||||
(define-key keymap [mouse-1] command)
|
||||
(cfw:tp text 'keymap keymap)
|
||||
(cfw:tp text 'mouse-face 'highlight)
|
||||
text))
|
|
@ -0,0 +1,58 @@
|
|||
;;; app/calendar/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +calendar-open-function #'+calendar/open-calendar
|
||||
"TODO")
|
||||
|
||||
|
||||
;;
|
||||
;; Packages
|
||||
|
||||
(use-package! calfw
|
||||
:commands cfw:open-calendar-buffer
|
||||
:config
|
||||
;; better frame for calendar
|
||||
(setq cfw:face-item-separator-color nil
|
||||
cfw:render-line-breaker 'cfw:render-line-breaker-none
|
||||
cfw:fchar-junction ?╋
|
||||
cfw:fchar-vertical-line ?┃
|
||||
cfw:fchar-horizontal-line ?━
|
||||
cfw:fchar-left-junction ?┣
|
||||
cfw:fchar-right-junction ?┫
|
||||
cfw:fchar-top-junction ?┯
|
||||
cfw:fchar-top-left-corner ?┏
|
||||
cfw:fchar-top-right-corner ?┓)
|
||||
|
||||
(define-key cfw:calendar-mode-map "q" #'+calendar/quit)
|
||||
|
||||
(add-hook 'cfw:calendar-mode-hook #'doom-mark-buffer-as-real-h)
|
||||
(add-hook 'cfw:calendar-mode-hook 'hide-mode-line-mode)
|
||||
|
||||
(advice-add #'cfw:render-button :override #'+calendar-cfw:render-button-a))
|
||||
|
||||
|
||||
(use-package! calfw-org
|
||||
:commands (cfw:open-org-calendar
|
||||
cfw:org-create-source
|
||||
cfw:org-create-file-source
|
||||
cfw:open-org-calendar-withkevin
|
||||
my-open-calendar))
|
||||
|
||||
(use-package! calfw-cal
|
||||
:commands (cfw:cal-create-source))
|
||||
|
||||
(use-package! calfw-ical
|
||||
:commands (cfw:ical-create-source))
|
||||
|
||||
|
||||
(use-package! org-gcal
|
||||
:commands (org-gcal-sync
|
||||
org-gcal-fetch
|
||||
org-gcal-post-at-point
|
||||
org-gcal-delete-at-point)
|
||||
:init
|
||||
(defvar org-gcal-dir (concat doom-cache-dir "org-gcal/"))
|
||||
(defvar org-gcal-token-file (concat org-gcal-dir "token.gpg"))
|
||||
:config
|
||||
;; hack to avoid the deferred.el error
|
||||
(defun org-gcal--notify (title mes)
|
||||
(message "org-gcal::%s - %s" title mes)))
|
|
@ -0,0 +1,8 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; app/calendar/packages.el
|
||||
|
||||
(package! calfw :pin "03abce97620a4a7f7ec5f911e669da9031ab9088")
|
||||
(package! calfw-org :pin "03abce97620a4a7f7ec5f911e669da9031ab9088")
|
||||
(package! calfw-cal :pin "03abce97620a4a7f7ec5f911e669da9031ab9088")
|
||||
(package! calfw-ical :pin "03abce97620a4a7f7ec5f911e669da9031ab9088")
|
||||
(package! org-gcal :pin "2cad2d8c175975dea42903cd4e3fd8bec423c01a")
|
|
@ -0,0 +1,164 @@
|
|||
#+TITLE: app/irc
|
||||
#+DATE: June 11, 2017
|
||||
#+SINCE: v2.0.3
|
||||
#+STARTUP: inlineimages
|
||||
|
||||
* Table of Contents :TOC:
|
||||
- [[#description][Description]]
|
||||
- [[#module-flags][Module Flags]]
|
||||
- [[#plugins][Plugins]]
|
||||
- [[#prerequisites][Prerequisites]]
|
||||
- [[#macos][macOS]]
|
||||
- [[#debian--ubuntu][Debian / Ubuntu]]
|
||||
- [[#arch-linux][Arch Linux]]
|
||||
- [[#nixos][NixOS]]
|
||||
- [[#features][Features]]
|
||||
- [[#an-irc-client-in-emacs][An IRC Client in Emacs]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#pass-the-unix-password-manager][Pass: the unix password manager]]
|
||||
- [[#emacs-auth-source-api][Emacs' auth-source API]]
|
||||
- [[#troubleshooting][Troubleshooting]]
|
||||
|
||||
* Description
|
||||
This module turns Emacs into an IRC client, capable of OS notifications.
|
||||
|
||||
** Module Flags
|
||||
This module provides no flags.
|
||||
|
||||
** Plugins
|
||||
+ [[https://github.com/jorgenschaefer/circe][circe]]
|
||||
+ [[https://github.com/eqyiel/circe-notifications][circe-notifications]]
|
||||
|
||||
* Prerequisites
|
||||
This module requires =gnutls= for secure IRC connections to work.
|
||||
|
||||
** macOS
|
||||
#+BEGIN_SRC sh
|
||||
brew install gnutls
|
||||
#+END_SRC
|
||||
|
||||
** Debian / Ubuntu
|
||||
#+BEGIN_SRC sh
|
||||
apt install gnutls-bin
|
||||
#+END_SRC
|
||||
|
||||
** Arch Linux
|
||||
#+BEGIN_SRC sh
|
||||
pacman -S gnutls
|
||||
#+END_SRC
|
||||
** NixOS
|
||||
#+BEGIN_SRC nix
|
||||
environment.systemPackages = [ pkgs.gnutls ];
|
||||
#+END_SRC
|
||||
|
||||
* Features
|
||||
** An IRC Client in Emacs
|
||||
To connect to IRC you can invoke the ~=irc~ function using =M-x= or your own
|
||||
custom keybinding.
|
||||
|
||||
| command | description |
|
||||
|---------+-------------------------------------------|
|
||||
| ~=irc~ | Connect to IRC and all configured servers |
|
||||
|
||||
When in a circe buffer these keybindings will be available.
|
||||
|
||||
| command | key | description |
|
||||
|-----------------------------+-----------+----------------------------------------------|
|
||||
| ~+irc/tracking-next-buffer~ | =SPC m a= | Switch to the next active buffer |
|
||||
| ~circe-command-JOIN~ | =SPC m j= | Join a channel |
|
||||
| ~+irc/send-message~ | =SPC m m= | Send a private message |
|
||||
| ~circe-command-NAMES~ | =SPC m n= | List the names of the current channel |
|
||||
| ~circe-command-PART~ | =SPC m p= | Part the current channel |
|
||||
| ~+irc/quit~ | =SPC m Q= | Kill the current circe session and workgroup |
|
||||
| ~circe-reconnect~ | =SPC m R= | Reconnect the current server |
|
||||
|
||||
* Configuration
|
||||
Use ~set-irc-server! SERVER PLIST~ to configure IRC servers. Its second argument (a plist)
|
||||
takes the same arguments as ~circe-network-options~.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
;; if you omit =:host=, ~SERVER~ will be used instead.
|
||||
(after! circe
|
||||
(set-irc-server! "chat.freenode.net"
|
||||
`(:tls t
|
||||
:port 6697
|
||||
:nick "doom"
|
||||
:sasl-username "myusername"
|
||||
:sasl-password "mypassword"
|
||||
:channels ("#emacs"))))
|
||||
#+END_SRC
|
||||
|
||||
However, *it is a obviously a bad idea to store your password in plaintext,* so
|
||||
here are ways to avoid that:
|
||||
|
||||
** Pass: the unix password manager
|
||||
[[https://www.passwordstore.org/][Pass]] is my tool of choice. I use it to manage my passwords. If you activate the
|
||||
[[../../../modules/tools/pass/README.org][:tools pass]] module you get an elisp API through which to access your
|
||||
password store.
|
||||
|
||||
~set-irc-server!~ accepts a plist can use functions instead of strings.
|
||||
~+pass-get-user~ and ~+pass-get-secret~ can help here:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(set-irc-server! "chat.freenode.net"
|
||||
`(:tls t
|
||||
:port 6697
|
||||
:nick "doom"
|
||||
:sasl-username ,(+pass-get-user "irc/freenode.net")
|
||||
:sasl-password ,(+pass-get-secret "irc/freenode.net")
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
But wait, there's more! This stores your password in a public variable which
|
||||
could be accessed or appear in backtraces. Not good! So we go a step further:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(set-irc-server! "chat.freenode.net"
|
||||
`(:tls t
|
||||
:port 6697
|
||||
:nick "doom"
|
||||
:sasl-username ,(+pass-get-user "irc/freenode.net")
|
||||
:sasl-password (lambda (&rest _) (+pass-get-secret "irc/freenode.net"))
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
And you're good to go!
|
||||
|
||||
Note that =+pass-get-user= tries to find your username by looking for the fields
|
||||
listed in =+pass-user-fields= (by default =login=, =user==, =username== and
|
||||
=email=)=). An example configuration looks like
|
||||
|
||||
#+begin_example
|
||||
mysecretpassword
|
||||
username: myusername
|
||||
#+end_example
|
||||
|
||||
** Emacs' auth-source API
|
||||
~auth-source~ is built into Emacs. As suggested [[https://github.com/jorgenschaefer/circe/wiki/Configuration#safer-password-management][in the circe wiki]], you can store
|
||||
(and retrieve) encrypted passwords with it.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(setq auth-sources '("~/.authinfo.gpg"))
|
||||
|
||||
(defun my-fetch-password (&rest params)
|
||||
(require 'auth-source)
|
||||
(let ((match (car (apply #'auth-source-search params))))
|
||||
(if match
|
||||
(let ((secret (plist-get match :secret)))
|
||||
(if (functionp secret)
|
||||
(funcall secret)
|
||||
secret))
|
||||
(error "Password not found for %S" params))))
|
||||
|
||||
(defun my-nickserv-password (server)
|
||||
(my-fetch-password :user "forcer" :host "irc.freenode.net"))
|
||||
|
||||
(set-irc-server! "chat.freenode.net"
|
||||
'(:tls t
|
||||
:port 6697
|
||||
:nick "doom"
|
||||
:sasl-password my-nickserver-password
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
* TODO Troubleshooting
|
|
@ -0,0 +1,121 @@
|
|||
;;; app/irc/autoload/email.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +irc--workspace-name "*IRC*")
|
||||
|
||||
(defun +irc-setup-wconf (&optional inhibit-workspace)
|
||||
(when (and (featurep! :ui workspaces)
|
||||
(not inhibit-workspace))
|
||||
(+workspace-switch +irc--workspace-name 'auto-create))
|
||||
(let ((buffers (doom-buffers-in-mode 'circe-mode nil t)))
|
||||
(if (not (member (window-buffer) buffers))
|
||||
(if buffers
|
||||
(ignore (switch-to-buffer (car buffers)))
|
||||
(require 'circe)
|
||||
(delete-other-windows)
|
||||
(switch-to-buffer (doom-fallback-buffer))
|
||||
t)
|
||||
(user-error "IRC buffer is already active and selected"))))
|
||||
|
||||
;;;###autoload
|
||||
(defun =irc (&optional inhibit-workspace)
|
||||
"Connect to IRC and auto-connect to all registered networks.
|
||||
|
||||
If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new
|
||||
workspace for it."
|
||||
(interactive "P")
|
||||
(+irc-setup-wconf inhibit-workspace)
|
||||
(cond ((doom-buffers-in-mode 'circe-mode (doom-buffer-list) t)
|
||||
(message "Circe buffers are already open"))
|
||||
(circe-network-options
|
||||
(mapc #'circe (mapcar #'car circe-network-options)))
|
||||
((call-interactively #'circe))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/connect (&optional inhibit-workspace)
|
||||
"Connect to a specific registered server.
|
||||
|
||||
If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new
|
||||
workspace for it."
|
||||
(interactive "P")
|
||||
(and (+irc-setup-wconf inhibit-workspace)
|
||||
(call-interactively #'circe)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/send-message (who what)
|
||||
"Send WHO a message containing WHAT."
|
||||
(interactive "sWho: \nsWhat: ")
|
||||
(circe-command-MSG who what))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/quit ()
|
||||
"Kill current circe session and workgroup."
|
||||
(interactive)
|
||||
(unless (y-or-n-p "Really kill IRC session?")
|
||||
(user-error "Aborted"))
|
||||
(let (circe-channel-killed-confirmation
|
||||
circe-server-killed-confirmation)
|
||||
(when +irc--defer-timer
|
||||
(cancel-timer +irc--defer-timer))
|
||||
(disable-circe-notifications)
|
||||
(mapc #'kill-buffer (doom-buffers-in-mode 'circe-mode (buffer-list) t))
|
||||
(when (featurep! :ui workspaces)
|
||||
(when (equal (+workspace-current-name) +irc--workspace-name)
|
||||
(+workspace/delete +irc--workspace-name)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/ivy-jump-to-channel (&optional this-server)
|
||||
"Jump to an open channel or server buffer with ivy. If THIS-SERVER (universal
|
||||
argument) is non-nil only show channels in current server."
|
||||
(interactive "P")
|
||||
(if (not (circe-server-buffers))
|
||||
(message "No circe buffers available")
|
||||
(when (and this-server (not circe-server-buffer))
|
||||
(setq this-server nil))
|
||||
(ivy-read (format "Jump to%s: " (if this-server (format " (%s)" (buffer-name circe-server-buffer)) ""))
|
||||
(cl-loop with servers = (if this-server (list circe-server-buffer) (circe-server-buffers))
|
||||
with current-buffer = (current-buffer)
|
||||
for server in servers
|
||||
collect (buffer-name server)
|
||||
nconc
|
||||
(with-current-buffer server
|
||||
(cl-loop for buf in (circe-server-chat-buffers)
|
||||
unless (eq buf current-buffer)
|
||||
collect (format " %s" (buffer-name buf)))))
|
||||
:action #'+irc--ivy-switch-to-buffer-action
|
||||
:preselect (buffer-name (current-buffer))
|
||||
:keymap ivy-switch-buffer-map
|
||||
:caller '+irc/ivy-jump-to-channel)))
|
||||
|
||||
(defun +irc--ivy-switch-to-buffer-action (buffer)
|
||||
(when (stringp buffer)
|
||||
(ivy--switch-buffer-action (string-trim-left buffer))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc/tracking-next-buffer ()
|
||||
"Disables switching to an unread buffer unless in the irc workspace."
|
||||
(interactive)
|
||||
(when (derived-mode-p 'circe-mode)
|
||||
(tracking-next-buffer)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Hooks/fns
|
||||
|
||||
;;;###autoload
|
||||
(defun +circe-buffer-p (buf)
|
||||
"Return non-nil if BUF is a `circe-mode' buffer."
|
||||
(with-current-buffer buf
|
||||
(derived-mode-p 'circe-mode)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +irc--add-circe-buffer-to-persp-h ()
|
||||
(when (and (bound-and-true-p persp-mode)
|
||||
(+workspace-exists-p +irc--workspace-name))
|
||||
(let ((persp (get-current-persp))
|
||||
(buf (current-buffer)))
|
||||
;; Add a new circe buffer to irc workspace when we're in another workspace
|
||||
(unless (eq (safe-persp-name persp) +irc--workspace-name)
|
||||
;; Add new circe buffers to the persp containing circe buffers
|
||||
(persp-add-buffer buf (persp-get-by-name +irc--workspace-name))
|
||||
;; Remove new buffer from accidental workspace
|
||||
(persp-remove-buffer buf persp)))))
|
|
@ -0,0 +1,17 @@
|
|||
;;; app/irc/autoload/settings.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autodef
|
||||
(defun set-irc-server! (server plist)
|
||||
"Registers an irc SERVER for circe.
|
||||
|
||||
SERVER can either be a name for the network (in which case you must specify a
|
||||
:host), or it may be the hostname itself, in which case it will be used as the
|
||||
:host.
|
||||
|
||||
See `circe-network-options' for details."
|
||||
(after! circe
|
||||
(unless (plist-member plist :host)
|
||||
(plist-put! plist :host server))
|
||||
(setf (alist-get server circe-network-options
|
||||
nil nil #'equal)
|
||||
plist)))
|
|
@ -0,0 +1,255 @@
|
|||
;;; app/irc/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +irc-left-padding 13
|
||||
"By how much spaces the left hand side of the line should be padded.
|
||||
Below a value of 12 this may result in uneven alignment between the various
|
||||
types of messages.")
|
||||
|
||||
(defvar +irc-truncate-nick-char ?…
|
||||
"Character to displayed when nick > `+irc-left-padding' in length.")
|
||||
|
||||
(defvar +irc-scroll-to-bottom-on-commands
|
||||
'(self-insert-command yank hilit-yank)
|
||||
"If these commands are called pre prompt the buffer will scroll to `point-max'.")
|
||||
|
||||
(defvar +irc-disconnect-hook nil
|
||||
"Runs each hook when circe noticies the connection has been disconnected.
|
||||
Useful for scenarios where an instant reconnect will not be successful.")
|
||||
|
||||
(defvar +irc-bot-list '("fsbot" "rudybot")
|
||||
"Nicks listed have `circe-fool-face' applied and will not be tracked.")
|
||||
|
||||
(defvar +irc-time-stamp-format "%H:%M"
|
||||
"The format of time stamps.
|
||||
|
||||
See `format-time-string' for a full description of available
|
||||
formatting directives. ")
|
||||
|
||||
(defvar +irc-notifications-watch-strings nil
|
||||
"A list of strings which can trigger a notification. You don't need to put
|
||||
your nick here.
|
||||
|
||||
See `circe-notifications-watch-strings'.")
|
||||
|
||||
(defvar +irc-defer-notifications nil
|
||||
"How long to defer enabling notifications, in seconds (e.g. 5min = 300).
|
||||
Useful for ZNC users who want to avoid the deluge of notifications during buffer
|
||||
playback.")
|
||||
|
||||
(defvar +irc--defer-timer nil)
|
||||
|
||||
(defsubst +irc--pad (left right)
|
||||
(format (format "%%%ds | %%s" +irc-left-padding)
|
||||
(concat "*** " left) right))
|
||||
|
||||
|
||||
;;
|
||||
;; Packages
|
||||
|
||||
(use-package! circe
|
||||
:commands circe circe-server-buffers
|
||||
:config
|
||||
(setq circe-default-quit-message nil
|
||||
circe-default-part-message nil
|
||||
circe-use-cycle-completion t
|
||||
circe-reduce-lurker-spam t
|
||||
|
||||
circe-format-say (format "{nick:+%ss} │ {body}" +irc-left-padding)
|
||||
circe-format-self-say circe-format-say
|
||||
circe-format-action (format "{nick:+%ss} * {body}" +irc-left-padding)
|
||||
circe-format-self-action circe-format-action
|
||||
circe-format-server-notice
|
||||
(let ((left "-Server-")) (concat (make-string (- +irc-left-padding (length left)) ? )
|
||||
(concat left " _ {body}")))
|
||||
circe-format-notice (format "{nick:%ss} _ {body}" +irc-left-padding)
|
||||
circe-format-server-topic
|
||||
(+irc--pad "Topic" "{userhost}: {topic-diff}")
|
||||
circe-format-server-join-in-channel
|
||||
(+irc--pad "Join" "{nick} ({userinfo}) joined {channel}")
|
||||
circe-format-server-join
|
||||
(+irc--pad "Join" "{nick} ({userinfo})")
|
||||
circe-format-server-part
|
||||
(+irc--pad "Part" "{nick} ({userhost}) left {channel}: {reason}")
|
||||
circe-format-server-quit
|
||||
(+irc--pad "Quit" "{nick} ({userhost}) left IRC: {reason}]")
|
||||
circe-format-server-quit-channel
|
||||
(+irc--pad "Quit" "{nick} ({userhost}) left {channel}: {reason}]")
|
||||
circe-format-server-rejoin
|
||||
(+irc--pad "Re-join" "{nick} ({userhost}), left {departuredelta} ago")
|
||||
circe-format-server-netmerge
|
||||
(+irc--pad "Netmerge" "{split}, split {ago} ago (Use /WL to see who's still missing)")
|
||||
circe-format-server-nick-change
|
||||
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
|
||||
circe-format-server-nick-change-self
|
||||
(+irc--pad "Nick" "You are now known as {new-nick} ({old-nick})")
|
||||
circe-format-server-nick-change-self
|
||||
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
|
||||
circe-format-server-mode-change
|
||||
(+irc--pad "Mode" "{change} on {target} by {setter} ({userhost})")
|
||||
circe-format-server-lurker-activity
|
||||
(+irc--pad "Lurk" "{nick} joined {joindelta} ago"))
|
||||
|
||||
(add-hook 'doom-real-buffer-functions #'+circe-buffer-p)
|
||||
(add-hook 'circe-channel-mode-hook #'turn-on-visual-line-mode)
|
||||
(add-hook 'circe-mode-hook #'+irc--add-circe-buffer-to-persp-h)
|
||||
(add-hook 'circe-mode-hook #'turn-off-smartparens-mode)
|
||||
|
||||
;; HACK Fix #1862: circe hangs on TLS connections when using OpenSSL versions
|
||||
;; > 1.1.0, where tls.el does not correctly determine the end of the info
|
||||
;; block. This fixes proposed in jorgenschaefer/circe#340
|
||||
(setq-hook! 'circe-mode-hook
|
||||
tls-end-of-info
|
||||
(concat "\\("
|
||||
;; `openssl s_client' regexp. See ssl/ssl_txt.c lines 219-220.
|
||||
;; According to apps/s_client.c line 1515 `---' is always the last
|
||||
;; line that is printed by s_client before the real data.
|
||||
"^ Verify return code: .+\n\\(\\|^ Extended master secret: .+\n\\)\\(\\|^ Max Early Data: .+\n\\)---\n\\|"
|
||||
;; `gnutls' regexp. See src/cli.c lines 721-.
|
||||
"^- Simple Client Mode:\n"
|
||||
"\\(\n\\|" ; ignore blank lines
|
||||
;; According to GnuTLS v2.1.5 src/cli.c lines 640-650 and 705-715 in
|
||||
;; `main' the handshake will start after this message. If the
|
||||
;; handshake fails, the programs will abort.
|
||||
"^\\*\\*\\* Starting TLS handshake\n\\)*"
|
||||
"\\)"))
|
||||
|
||||
(defadvice! +irc--circe-run-disconnect-hook-a (&rest _)
|
||||
"Runs `+irc-disconnect-hook' after circe disconnects."
|
||||
:after #'circe--irc-conn-disconnected
|
||||
(run-hooks '+irc-disconnect-hook))
|
||||
|
||||
(add-hook! 'lui-pre-output-hook
|
||||
(defun +irc-circe-truncate-nicks-h ()
|
||||
"Truncate long nicknames in chat output non-destructively."
|
||||
(when-let (beg (text-property-any (point-min) (point-max) 'lui-format-argument 'nick))
|
||||
(goto-char beg)
|
||||
(let ((end (next-single-property-change beg 'lui-format-argument))
|
||||
(nick (plist-get (plist-get (text-properties-at beg) 'lui-keywords)
|
||||
:nick)))
|
||||
(when (> (length nick) +irc-left-padding)
|
||||
(compose-region (+ beg +irc-left-padding -1) end
|
||||
+irc-truncate-nick-char))))))
|
||||
|
||||
(add-hook! 'circe-message-option-functions
|
||||
(defun +irc-circe-message-option-bot-h (nick &rest ignored)
|
||||
"Fontify known bots and mark them to not be tracked."
|
||||
(when (member nick +irc-bot-list)
|
||||
'((text-properties . (face circe-fool-face lui-do-not-track t))))))
|
||||
|
||||
;; Let `+irc/quit' and `circe' handle buffer cleanup
|
||||
(define-key circe-mode-map [remap kill-buffer] #'bury-buffer)
|
||||
;; Fail gracefully if not in a circe buffer
|
||||
(global-set-key [remap tracking-next-buffer] #'+irc/tracking-next-buffer)
|
||||
|
||||
(map! :localleader
|
||||
(:map circe-mode-map
|
||||
"a" #'tracking-next-buffer
|
||||
"j" #'circe-command-JOIN
|
||||
"m" #'+irc/send-message
|
||||
"p" #'circe-command-PART
|
||||
"Q" #'+irc/quit
|
||||
"R" #'circe-reconnect
|
||||
(:when (featurep! :completion ivy)
|
||||
"c" #'+irc/ivy-jump-to-channel))
|
||||
(:map circe-channel-mode-map
|
||||
"n" #'circe-command-NAMES)))
|
||||
|
||||
|
||||
(use-package! circe-color-nicks
|
||||
:hook (circe-channel-mode . enable-circe-color-nicks)
|
||||
:config
|
||||
(setq circe-color-nicks-min-constrast-ratio 4.5
|
||||
circe-color-nicks-everywhere t))
|
||||
|
||||
|
||||
(use-package! circe-new-day-notifier
|
||||
:after circe
|
||||
:config
|
||||
(enable-circe-new-day-notifier)
|
||||
(setq circe-new-day-notifier-format-message
|
||||
(+irc--pad "Day" "Date changed [{day}]")))
|
||||
|
||||
|
||||
(use-package! circe-notifications
|
||||
:commands enable-circe-notifications
|
||||
:init
|
||||
(add-hook! 'circe-server-connected-hook
|
||||
(defun +irc-init-circe-notifications-h ()
|
||||
(if (numberp +irc-defer-notifications)
|
||||
(setq +irc--defer-timer
|
||||
(run-at-time +irc-defer-notifications nil
|
||||
#'enable-circe-notifications))
|
||||
(enable-circe-notifications))))
|
||||
:config
|
||||
(setq circe-notifications-watch-strings +irc-notifications-watch-strings
|
||||
circe-notifications-emacs-focused nil
|
||||
circe-notifications-alert-style
|
||||
(cond (IS-MAC 'osx-notifier)
|
||||
(IS-LINUX 'libnotify)
|
||||
(circe-notifications-alert-style))))
|
||||
|
||||
|
||||
(use-package! lui
|
||||
:commands lui-mode
|
||||
:config
|
||||
(define-key lui-mode-map "\C-u" #'lui-kill-to-beginning-of-line)
|
||||
(setq lui-fill-type nil)
|
||||
|
||||
(when (featurep! :checkers spell)
|
||||
(setq lui-flyspell-p t))
|
||||
|
||||
(after! evil
|
||||
(defun +irc-evil-insert-h ()
|
||||
"Ensure entering insert mode will put us at the prompt, unless editing
|
||||
after prompt marker."
|
||||
(when (> (marker-position lui-input-marker) (point))
|
||||
(goto-char (point-max))))
|
||||
|
||||
(add-hook! 'lui-mode-hook
|
||||
(add-hook 'evil-insert-state-entry-hook #'+irc-evil-insert-h
|
||||
nil 'local))
|
||||
|
||||
(mapc (lambda (cmd) (push cmd +irc-scroll-to-bottom-on-commands))
|
||||
'(evil-paste-after evil-paste-before evil-open-above evil-open-below)))
|
||||
|
||||
|
||||
(defun +irc-preinput-scroll-to-bottom-h ()
|
||||
"Go to the end of the buffer in all windows showing it.
|
||||
Courtesy of esh-mode.el"
|
||||
(when (memq this-command +irc-scroll-to-bottom-on-commands)
|
||||
(let* ((selected (selected-window))
|
||||
(current (current-buffer)))
|
||||
(when (> (marker-position lui-input-marker) (point))
|
||||
(walk-windows
|
||||
(function
|
||||
(lambda (window)
|
||||
(when (eq (window-buffer window) current)
|
||||
(select-window window)
|
||||
(goto-char (point-max))
|
||||
(select-window selected))))
|
||||
nil t)))))
|
||||
|
||||
(add-hook! 'lui-mode-hook
|
||||
(add-hook 'pre-command-hook #'+irc-preinput-scroll-to-bottom-h nil t))
|
||||
|
||||
;; enable a horizontal line marking the last read message
|
||||
(add-hook 'lui-mode-hook #'enable-lui-track-bar)
|
||||
|
||||
(add-hook! 'lui-mode-hook
|
||||
(defun +irc-init-lui-margins-h ()
|
||||
(setq lui-time-stamp-position 'right-margin
|
||||
lui-time-stamp-format +irc-time-stamp-format
|
||||
right-margin-width (length (format-time-string lui-time-stamp-format))))
|
||||
(defun +irc-init-lui-wrapping-a ()
|
||||
(setq fringes-outside-margins t
|
||||
word-wrap t
|
||||
wrap-prefix (make-string (+ +irc-left-padding 3) ? )))))
|
||||
|
||||
|
||||
(use-package! lui-logging
|
||||
:after lui
|
||||
:config (enable-lui-logging))
|
||||
|
||||
|
||||
(use-package! lui-autopaste
|
||||
:hook (circe-channel-mode . enable-lui-autopaste))
|
|
@ -0,0 +1,5 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; app/irc/packages.el
|
||||
|
||||
(package! circe :pin "d98986ce933c380b47d727beea8bad81bda65dc9")
|
||||
(package! circe-notifications :pin "291149ac12877bbd062da993479d3533a26862b0")
|
|
@ -0,0 +1,112 @@
|
|||
#+TITLE: app/rss
|
||||
#+DATE: May 12, 2020
|
||||
#+SINCE: v2.0.9
|
||||
#+STARTUP: inlineimages nofold
|
||||
|
||||
* Table of Contents :TOC_3:noexport:
|
||||
- [[#description][Description]]
|
||||
- [[#maintainers][Maintainers]]
|
||||
- [[#module-flags][Module Flags]]
|
||||
- [[#plugins][Plugins]]
|
||||
- [[#hacks][Hacks]]
|
||||
- [[#prerequisites][Prerequisites]]
|
||||
- [[#features][Features]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#without-org][Without +org]]
|
||||
- [[#with-org][With +org]]
|
||||
- [[#keybindings][Keybindings]]
|
||||
- [[#news-filtering][News filtering]]
|
||||
- [[#automatically-updating-feed-when-opening-elfeed][Automatically updating feed when opening elfeed]]
|
||||
- [[#troubleshooting][Troubleshooting]]
|
||||
|
||||
* Description
|
||||
+ Read RSS feeds in the comfort of DOOM (Emacs)
|
||||
|
||||
** Maintainers
|
||||
This module has no dedicated maintainers.
|
||||
|
||||
** Module Flags
|
||||
+ =+org= to enable ~elfeed-org~ to use ~org-directory/elfeed.org~
|
||||
|
||||
** Plugins
|
||||
+ [[https://github.com/skeeto/elfeed][elfeed]]
|
||||
+ =+org=
|
||||
+ [[https://github.com/remyhonig/elfeed-org][elfeed-org]]
|
||||
|
||||
** Hacks
|
||||
+ By default ~elfeed-search-filter~ is set to ~@2-weeks-ago~ and makes the last 2 weeks of entries visible. This needs to be set after elfeed has loaded like so in your ~config.el~
|
||||
#+begin_src elisp
|
||||
(after! elfeed
|
||||
(setq elfeed-search-filter "@1-month-ago +unread"))
|
||||
#+end_src
|
||||
|
||||
* Prerequisites
|
||||
This module has no prerequisites.
|
||||
|
||||
* Features
|
||||
+ As there isn't currently binding for ~elfeed-update~ you can run it with ~M-x elfeed-update~
|
||||
|
||||
* Configuration
|
||||
** Without +org
|
||||
When you don't want to use org mode to manage your elfeed feeds you can put your subscriptions to personal ~config.el~ file, ex:
|
||||
#+BEGIN_SRC elisp
|
||||
(setq elfeed-feeds
|
||||
'("https://this-week-in-rust.org/rss.xml"
|
||||
"http://feeds.bbci.co.uk/news/rss.xml"))
|
||||
#+END_SRC
|
||||
|
||||
** With +org
|
||||
When using ~+org~ flag then configuration is easier. You can use ~org-mode~ to configure feeds to follow.
|
||||
#+BEGIN_SRC org
|
||||
,* root :elfeed:
|
||||
,** Programming :programming:
|
||||
,*** [[https://this-week-in-rust.org/rss.xml][This Week in Rust]] :rust:
|
||||
,** News :news:
|
||||
,*** Top news :tops:
|
||||
,**** http://feeds.bbci.co.uk/news/rss.xml
|
||||
#+END_SRC
|
||||
+ Root of ~elfeed-org~ needs to have ~:elfeed:~ tag. This is where ~elfeed-org~ starts to read.
|
||||
+ You can have subheaders as in example ~:programming:~, and ~elfeed-org~ applies that tag to all subheader feeds, in example it adds it to ~This Week in Rust~.
|
||||
+ You can "name" feeds as you please with ~org-mode~ ~org-insert-link~ (~C-c C-l~) and put name as you want into ~description~.
|
||||
+ If you don't want to use ~org-directory/elfeed.org~ file you can specify it with ~(setq rmh-elfeed-org-files ("path/to/your/elfeed/file.org))~
|
||||
|
||||
** Keybindings
|
||||
+ General
|
||||
| Key | Mode | Description |
|
||||
|---------+--------------------+------------------------|
|
||||
| =S-RET= | Elfeed-search-mode | Open link into browser |
|
||||
| =RET= | Elfeed-search-mode | Open item |
|
||||
| =s= | Elfeed-search-mode | Filter |
|
||||
| =C-j= | Elfeed-show-mode | Move to next item |
|
||||
| =C-k= | Elfeed-show-mode | Move to previous item |
|
||||
|
||||
+ If ~:editor evil +everywhere~
|
||||
| Key | Description |
|
||||
|-----+-----------------------------|
|
||||
| q | elfeed-kill-buffer |
|
||||
| r | elfeed-search-update--force |
|
||||
|
||||
** News filtering
|
||||
+ Time filtering
|
||||
+ ~@2-days-ago~ Past two days
|
||||
+ ~@2-weeks-ago~ Past two weeks
|
||||
+ ~@2-years-ago~ Past two years
|
||||
+ ~@2020-06-19~ To show specific day
|
||||
+ ~@2020-06-19--2020-03-10~ Span of time
|
||||
+ Tag filtering
|
||||
+ Include ~+news~
|
||||
+ Exclude ~-rust~
|
||||
+ Both ~+news -rust~
|
||||
+ String filtering, this is case insensitive
|
||||
+ Include
|
||||
+ ~DOOM~
|
||||
+ ~Linu[sx]~ Search for both ~Linus~ and ~Linux~
|
||||
+ Exclude ~!something~
|
||||
|
||||
** Automatically updating feed when opening elfeed
|
||||
Hook ~elfeed-update~ to ~elfeed-search-mode-hook~
|
||||
#+BEGIN_SRC elisp
|
||||
(add-hook! 'elfeed-search-mode-hook 'elfeed-update)
|
||||
#+END_SRC
|
||||
|
||||
* TODO Troubleshooting
|
|
@ -0,0 +1,116 @@
|
|||
;;; app/rss/autoload.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autoload
|
||||
(defalias '=rss #'elfeed
|
||||
"Activate (or switch to) `elfeed' in its workspace.")
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss/delete-pane ()
|
||||
"Delete the *elfeed-entry* split pane."
|
||||
(interactive)
|
||||
(let* ((buf (get-buffer "*elfeed-entry*"))
|
||||
(window (get-buffer-window buf)))
|
||||
(delete-window window)
|
||||
(when (buffer-live-p buf)
|
||||
(kill-buffer buf))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss/open (entry)
|
||||
"Display the currently selected item in a buffer."
|
||||
(interactive (list (elfeed-search-selected :ignore-region)))
|
||||
(when (elfeed-entry-p entry)
|
||||
(elfeed-untag entry 'unread)
|
||||
(elfeed-search-update-entry entry)
|
||||
(elfeed-show-entry entry)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss/next ()
|
||||
"Show the next item in the elfeed-search buffer."
|
||||
(interactive)
|
||||
(funcall elfeed-show-entry-delete)
|
||||
(with-current-buffer (elfeed-search-buffer)
|
||||
(forward-line)
|
||||
(call-interactively '+rss/open)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss/previous ()
|
||||
"Show the previous item in the elfeed-search buffer."
|
||||
(interactive)
|
||||
(funcall elfeed-show-entry-delete)
|
||||
(with-current-buffer (elfeed-search-buffer)
|
||||
(forward-line -1)
|
||||
(call-interactively '+rss/open)))
|
||||
|
||||
|
||||
;;
|
||||
;; Hooks
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss-elfeed-wrap-h ()
|
||||
"Enhances an elfeed entry's readability by wrapping it to a width of
|
||||
`fill-column'."
|
||||
(let ((inhibit-read-only t)
|
||||
(inhibit-modification-hooks t))
|
||||
(setq-local truncate-lines nil)
|
||||
(setq-local shr-use-fonts nil)
|
||||
(setq-local shr-width 85)
|
||||
(set-buffer-modified-p nil)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss-cleanup-h ()
|
||||
"Clean up after an elfeed session. Kills all elfeed and elfeed-org files."
|
||||
(interactive)
|
||||
;; `delete-file-projectile-remove-from-cache' slows down `elfeed-db-compact'
|
||||
;; tremendously, so we disable the projectile cache:
|
||||
(let (projectile-enable-caching)
|
||||
(elfeed-db-compact))
|
||||
(let ((buf (previous-buffer)))
|
||||
(when (or (null buf) (not (doom-real-buffer-p buf)))
|
||||
(switch-to-buffer (doom-fallback-buffer))))
|
||||
(let ((search-buffers (doom-buffers-in-mode 'elfeed-search-mode))
|
||||
(show-buffers (doom-buffers-in-mode 'elfeed-show-mode))
|
||||
kill-buffer-query-functions)
|
||||
(dolist (file (bound-and-true-p rmh-elfeed-org-files))
|
||||
(when-let (buf (get-file-buffer (expand-file-name file org-directory)))
|
||||
(kill-buffer buf)))
|
||||
(dolist (b search-buffers)
|
||||
(with-current-buffer b
|
||||
(remove-hook 'kill-buffer-hook #'+rss-cleanup-h :local)
|
||||
(kill-buffer b)))
|
||||
(mapc #'kill-buffer show-buffers)))
|
||||
|
||||
|
||||
;;
|
||||
;; Functions
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss-dead-feeds (&optional years)
|
||||
"Return a list of feeds that haven't posted anything in YEARS."
|
||||
(let* ((years (or years 1.0))
|
||||
(living-feeds (make-hash-table :test 'equal))
|
||||
(seconds (* years 365.0 24 60 60))
|
||||
(threshold (- (float-time) seconds)))
|
||||
(with-elfeed-db-visit (entry feed)
|
||||
(let ((date (elfeed-entry-date entry)))
|
||||
(when (> date threshold)
|
||||
(setf (gethash (elfeed-feed-url feed) living-feeds) t))))
|
||||
(cl-loop for url in (elfeed-feed-list)
|
||||
unless (gethash url living-feeds)
|
||||
collect url)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss-put-sliced-image-fn (spec alt &optional flags)
|
||||
"TODO"
|
||||
(letf! (defun insert-image (image &optional alt _area _slice)
|
||||
(let ((height (cdr (image-size image t))))
|
||||
(insert-sliced-image image alt nil (max 1 (/ height 20.0)) 1)))
|
||||
(shr-put-image spec alt flags)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +rss-render-image-tag-without-underline-fn (dom &optional url)
|
||||
"TODO"
|
||||
(let ((start (point)))
|
||||
(shr-tag-img dom url)
|
||||
;; And remove underlines in case images are links, otherwise we get an
|
||||
;; underline beneath every slice.
|
||||
(put-text-property start (point) 'face '(:underline nil))))
|
|
@ -0,0 +1,74 @@
|
|||
;;; app/rss/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
;; This is an opinionated workflow that turns Emacs into an RSS reader, inspired
|
||||
;; by apps Reeder and Readkit. It can be invoked via `=rss'. Otherwise, if you
|
||||
;; don't care for the UI you can invoke elfeed directly with `elfeed'.
|
||||
|
||||
(defvar +rss-split-direction 'below
|
||||
"What direction to pop up the entry buffer in elfeed.")
|
||||
|
||||
(defvar +rss-enable-sliced-images t
|
||||
"Automatically slice images shown in elfeed-show-mode buffers, making them
|
||||
easier to scroll through.")
|
||||
|
||||
|
||||
;;
|
||||
;; Packages
|
||||
|
||||
(use-package! elfeed
|
||||
:commands elfeed
|
||||
:init
|
||||
(setq elfeed-db-directory (concat doom-local-dir "elfeed/db/")
|
||||
elfeed-enclosure-default-dir (concat doom-local-dir "elfeed/enclosures/"))
|
||||
:config
|
||||
(setq elfeed-search-filter "@2-week-ago "
|
||||
elfeed-show-entry-switch #'pop-to-buffer
|
||||
elfeed-show-entry-delete #'+rss/delete-pane
|
||||
shr-max-image-proportion 0.8)
|
||||
|
||||
(set-popup-rule! "^\\*elfeed-entry"
|
||||
:size 0.75 :actions '(display-buffer-below-selected)
|
||||
:select t :quit nil :ttl t)
|
||||
|
||||
(make-directory elfeed-db-directory t)
|
||||
|
||||
;; Ensure elfeed buffers are treated as real
|
||||
(add-hook! 'doom-real-buffer-functions
|
||||
(defun +rss-buffer-p (buf)
|
||||
(string-match-p "^\\*elfeed" (buffer-name buf))))
|
||||
|
||||
;; Enhance readability of a post
|
||||
(add-hook 'elfeed-show-mode-hook #'+rss-elfeed-wrap-h)
|
||||
(add-hook! 'elfeed-search-mode-hook
|
||||
(add-hook 'kill-buffer-hook #'+rss-cleanup-h nil 'local))
|
||||
|
||||
;; Large images are annoying to scroll through, because scrolling follows the
|
||||
;; cursor, so we force shr to insert images in slices.
|
||||
(when +rss-enable-sliced-images
|
||||
(setq-hook! 'elfeed-show-mode-hook
|
||||
shr-put-image-function #'+rss-put-sliced-image-fn
|
||||
shr-external-rendering-functions '((img . +rss-render-image-tag-without-underline-fn))))
|
||||
|
||||
;; Keybindings
|
||||
(after! elfeed-show
|
||||
(define-key! elfeed-show-mode-map
|
||||
[remap next-buffer] #'+rss/next
|
||||
[remap previous-buffer] #'+rss/previous))
|
||||
(when (featurep! :editor evil +everywhere)
|
||||
(evil-define-key 'normal elfeed-search-mode-map
|
||||
"q" #'elfeed-kill-buffer
|
||||
"r" #'elfeed-search-update--force
|
||||
(kbd "M-RET") #'elfeed-search-browse-url)))
|
||||
|
||||
|
||||
(use-package! elfeed-org
|
||||
:when (featurep! +org)
|
||||
:after elfeed
|
||||
:preface
|
||||
(setq rmh-elfeed-org-files (list "/home/dorian/.config/doom/elfeed.org"))
|
||||
:config
|
||||
(and (let ((default-directory org-directory))
|
||||
(setq rmh-elfeed-org-files
|
||||
(cl-remove-if-not
|
||||
#'file-exists-p (mapcar #'expand-file-name rmh-elfeed-org-files))))
|
||||
(elfeed-org)))
|
|
@ -0,0 +1,5 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; app/rss/packages.el
|
||||
|
||||
(package! elfeed :pin "7b2b6fadaa498fef2ba212a50da4a8afa2a5d305")
|
||||
(package! elfeed-org :pin "77b6bbf222487809813de260447d31c4c59902c9")
|
|
@ -0,0 +1,96 @@
|
|||
#+TITLE: app/twitter
|
||||
#+DATE: October 11, 2019
|
||||
#+SINCE: v2.0
|
||||
#+STARTUP: inlineimages
|
||||
|
||||
* Table of Contents :TOC_3:noexport:
|
||||
- [[#description][Description]]
|
||||
- [[#module-flags][Module Flags]]
|
||||
- [[#plugins][Plugins]]
|
||||
- [[#hacks][Hacks]]
|
||||
- [[#prerequisites][Prerequisites]]
|
||||
- [[#features][Features]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#troubleshooting][Troubleshooting]]
|
||||
- [[#appendix][Appendix]]
|
||||
- [[#commands--keybindings][Commands & Keybindings]]
|
||||
|
||||
* Description
|
||||
Enjoy twitter from emacs.
|
||||
|
||||
+ View various timelines side by side, e.g. user's timeline, home, etc.
|
||||
+ Post new tweets
|
||||
+ Send direct messages
|
||||
+ Retweet
|
||||
+ Follow and un-follow users
|
||||
+ Favorite tweets
|
||||
|
||||
** Module Flags
|
||||
This module provides no flags.
|
||||
|
||||
** Plugins
|
||||
+ [[https://github.com/hayamiz/twittering-mode][twittering-mode]]
|
||||
+ [[https://github.com/abo-abo/avy][avy]]
|
||||
|
||||
** TODO Hacks
|
||||
{A list of internal modifications to included packages}
|
||||
|
||||
* Prerequisites
|
||||
|
||||
+ For SSL connection (required by Twitter's API), one of the followings is required:
|
||||
+ [[http://curl.haxx.se/][cURL]]
|
||||
+ [[http://www.gnu.org/software/wget/][GNU Wget]]
|
||||
+ [[http://www.openssl.org/][OpenSSL]]
|
||||
+ [[http://www.gnu.org/software/gnutls/][GnuTLS]]
|
||||
+ [[http://www.gnupg.org/][GnuPG]] is required for keeping the OAuth token encrypted in local storage.
|
||||
+ ~twittering-icon-mode~ converts retrieved icons into XPM by default. To
|
||||
achieve this and for displaying icons in formats that are not supported by
|
||||
Emacs as well as for resizing icon images, [[http://www.imagemagick.org/][ImageMagick]] is required.
|
||||
|
||||
To build emacs with ImageMagick support the ~--with-imagemagick~ flag needs to
|
||||
be passed to the ~configure~ script, e.g. ~./configure --with-imagemagick~.
|
||||
For detailed instruction on how to build Emacs from source please refer to
|
||||
[[https://git.savannah.gnu.org/cgit/emacs.git/tree/INSTALL][INSTALL]] in Emacs' savannah repository.
|
||||
+ For keeping retrieved icons in local storage, [[http://www.gzip.org/][gzip]] is required.
|
||||
|
||||
* TODO Features
|
||||
An in-depth list of features, how to use them, and their dependencies.
|
||||
|
||||
* TODO Configuration
|
||||
How to configure this module, including common problems and how to address them.
|
||||
|
||||
* Troubleshooting
|
||||
Currently ~twittering-mode~ binds =SPC=, breaking its functionality as a leader
|
||||
key. To work around this issue you may use =M-SPC= instead when in
|
||||
~twittering-mode~.
|
||||
|
||||
* Appendix
|
||||
** Commands & Keybindings
|
||||
Here is a list of available commands and their default keybindings (defined in
|
||||
[[./config.el][config.el]]).
|
||||
|
||||
| command | key / ex command | description |
|
||||
|---------------------+------------------+-------------------------------------------------------------|
|
||||
| ~+twitter/quit~ | =q= | Close current window |
|
||||
| ~+twitter/quit-all~ | =Q= | Close all twitter windows and buffers, and delete workspace |
|
||||
|
||||
And when ~:editor evil +everywhere~ is active:
|
||||
|
||||
| command | key / ex command | description |
|
||||
|--------------------------------------------------+------------------+------------------------------------------------------------------|
|
||||
| ~twittering-favorite~ | =f= | Favorite/Like a tweet |
|
||||
| ~twittering-unfavorite~ | =F= | Un-favorite/Un-like a tweet |
|
||||
| ~twittering-follow~ | =C-f= | Follow user |
|
||||
| ~twittering-unfollow~ | =C-F= | Un-follow user |
|
||||
| ~twittering-delete-status~ | =d= | Delete a tweet |
|
||||
| ~twittering-retweet~ | =r= | Retweet |
|
||||
| ~twittering-toggle-or-retrieve-replied-statuses~ | =R= | Toggle or retrieve replies |
|
||||
| ~twittering-update-status-interactive~ | =o= | Update tweets |
|
||||
| ~+twitter/ace-link~ | =O= | Open some visible link from a ~twittering-mode~ buffer using ace |
|
||||
| ~twittering-search~ | =/= | Search |
|
||||
| ~twittering-goto-next-status~ | =J= | Go to next tweet |
|
||||
| ~twittering-goto-previous-status~ | =K= | Go to previous tweet |
|
||||
| ~twittering-goto-first-status~ | =gg= | Go to first tweet |
|
||||
| ~twittering-goto-last-status~ | =G= | Go to last tweet |
|
||||
| ~twittering-goto-next-status-of-user~ | =gj= | Go to next tweet of user |
|
||||
| ~twittering-goto-previous-status-of-user)))~ | =gk= | Go to previous tweet of user |
|
|
@ -0,0 +1,104 @@
|
|||
;;; app/twitter/autoload.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +twitter-workspace-name "*Twitter*"
|
||||
"The name to use for the twitter workspace.")
|
||||
|
||||
;;;###autoload
|
||||
(defun +twitter-display-buffer-fn (buf)
|
||||
"A replacement display-buffer command for `twittering-pop-to-buffer-function'
|
||||
that works with the feature/popup module."
|
||||
(let ((win (selected-window)))
|
||||
(display-buffer buf)
|
||||
;; This is required because the new window generated by `pop-to-buffer'
|
||||
;; may hide the region following the current position.
|
||||
(twittering-ensure-whole-of-status-is-visible win)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +twitter-buffer-p (buf)
|
||||
"Return non-nil if BUF is a `twittering-mode' buffer."
|
||||
(eq 'twittering-mode (buffer-local-value 'major-mode buf)))
|
||||
|
||||
|
||||
;;
|
||||
;; Commands
|
||||
|
||||
(defvar +twitter--old-wconf nil)
|
||||
;;;###autoload
|
||||
(defun =twitter (arg)
|
||||
"Opens a workspace dedicated to `twittering-mode'."
|
||||
(interactive "P")
|
||||
(condition-case _
|
||||
(progn
|
||||
(if (and (not arg) (featurep! :ui workspaces))
|
||||
(+workspace/new +twitter-workspace-name)
|
||||
(setq +twitter--old-wconf (current-window-configuration))
|
||||
(delete-other-windows)
|
||||
(switch-to-buffer (doom-fallback-buffer)))
|
||||
(call-interactively #'twit)
|
||||
(unless (get-buffer (car twittering-initial-timeline-spec-string))
|
||||
(error "Failed to open twitter"))
|
||||
(switch-to-buffer (car twittering-initial-timeline-spec-string))
|
||||
(dolist (name (cdr twittering-initial-timeline-spec-string))
|
||||
(split-window-horizontally)
|
||||
(switch-to-buffer name))
|
||||
(balance-windows)
|
||||
(call-interactively #'+twitter/rerender-all))
|
||||
('error (+twitter/quit-all))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +twitter/quit ()
|
||||
"Close the current `twitter-mode' buffer."
|
||||
(interactive)
|
||||
(when (eq major-mode 'twittering-mode)
|
||||
(twittering-kill-buffer)
|
||||
(cond ((one-window-p) (+twitter/quit-all))
|
||||
((featurep! :ui workspaces)
|
||||
(+workspace/close-window-or-workspace))
|
||||
((delete-window)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +twitter/quit-all ()
|
||||
"Close all open `twitter-mode' buffers and the associated workspace, if any."
|
||||
(interactive)
|
||||
(when (featurep! :ui workspaces)
|
||||
(+workspace/delete +twitter-workspace-name))
|
||||
(when +twitter--old-wconf
|
||||
(set-window-configuration +twitter--old-wconf)
|
||||
(setq +twitter--old-wconf nil))
|
||||
(dolist (buf (doom-buffers-in-mode 'twittering-mode (buffer-list) t))
|
||||
(twittering-kill-buffer buf)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +twitter/rerender-all ()
|
||||
"Rerender all `twittering-mode' buffers."
|
||||
(interactive)
|
||||
(dolist (buf (doom-buffers-in-mode 'twittering-mode (buffer-list) t))
|
||||
(with-current-buffer buf
|
||||
(twittering-rerender-timeline-all buf)
|
||||
(setq-local line-spacing 0.2)
|
||||
(goto-char (point-min)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +twitter/ace-link ()
|
||||
"Open a visible link, username or hashtag in a `twittering-mode' buffer."
|
||||
(interactive)
|
||||
(require 'avy)
|
||||
;; REVIEW Is this necessary anymore with `link-hint'
|
||||
(let ((pt (avy-with +twitter/ace-link
|
||||
(avy--process
|
||||
(+twitter--collect-links)
|
||||
(avy--style-fn avy-style)))))
|
||||
(when (number-or-marker-p pt)
|
||||
(goto-char pt)
|
||||
(let ((uri (get-text-property (point) 'uri)))
|
||||
(if uri (browse-url uri))))))
|
||||
|
||||
(defun +twitter--collect-links ()
|
||||
(let ((end (window-end))
|
||||
points)
|
||||
(save-excursion
|
||||
(goto-char (window-start))
|
||||
(while (and (< (point) end)
|
||||
(ignore-errors (twittering-goto-next-thing) t))
|
||||
(push (point) points))
|
||||
(nreverse points))))
|
|
@ -0,0 +1,79 @@
|
|||
;;; app/twitter/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package! twittering-mode
|
||||
:commands twit
|
||||
:config
|
||||
(setq twittering-private-info-file
|
||||
(expand-file-name "twittering-mode.gpg" doom-etc-dir)
|
||||
twittering-use-master-password t
|
||||
twittering-request-confirmation-on-posting t
|
||||
;; twittering-icon-mode t
|
||||
;; twittering-use-icon-storage t
|
||||
;; twittering-icon-storage-file (concat doom-cache-dir "twittering-mode-icons.gz")
|
||||
;; twittering-convert-fix-size 12
|
||||
twittering-timeline-header ""
|
||||
twittering-timeline-footer ""
|
||||
twittering-edit-skeleton 'inherit-any
|
||||
twittering-status-format "%FACE[font-lock-function-name-face]{ @%s} %FACE[italic]{%@} %FACE[error]{%FIELD-IF-NONZERO[❤ %d]{favorite_count}} %FACE[warning]{%FIELD-IF-NONZERO[↺ %d]{retweet_count}}
|
||||
%FOLD[ ]{%FILL{%t}%QT{
|
||||
%FOLD[ ]{%FACE[font-lock-function-name-face]{@%s}\t%FACE[shadow]{%@}
|
||||
%FOLD[ ]{%FILL{%t}}
|
||||
}}}
|
||||
|
||||
%FACE[twitter-divider]{ }
|
||||
"
|
||||
;; twittering-timeline-spec-alias '()
|
||||
twittering-initial-timeline-spec-string
|
||||
'(":home" ":mentions" ":direct_messages"))
|
||||
|
||||
(set-popup-rule! "^\\*twittering-edit" :size 15 :ttl nil :quit nil :select t)
|
||||
|
||||
(defface twitter-divider
|
||||
'((((background dark)) (:underline (:color "#141519")))
|
||||
(((background light)) (:underline (:color "#d3d3d3"))))
|
||||
"The vertical divider between tweets."
|
||||
:group 'twittering-mode)
|
||||
|
||||
(add-hook 'doom-real-buffer-functions #'+twitter-buffer-p)
|
||||
(when (featurep! :ui popup)
|
||||
(setq twittering-pop-to-buffer-function #'+twitter-display-buffer-fn))
|
||||
|
||||
(after! solaire-mode
|
||||
(add-hook 'twittering-mode-hook #'solaire-mode))
|
||||
|
||||
;; Custom header-line for twitter buffers
|
||||
(add-hook! 'twittering-mode-hook
|
||||
(defun +twitter-switch-mode-and-header-line-h ()
|
||||
(setq header-line-format mode-line-format
|
||||
mode-line-format nil)))
|
||||
|
||||
;; `epa--decode-coding-string' isn't defined in later versions of Emacs 27
|
||||
(unless (fboundp 'epa--decode-coding-string)
|
||||
(defalias 'epa--decode-coding-string #'decode-coding-string))
|
||||
|
||||
(define-key! twittering-mode-map
|
||||
"q" #'+twitter/quit
|
||||
"Q" #'+twitter/quit-all
|
||||
[remap twittering-kill-buffer] #'+twitter/quit
|
||||
[remap delete-window] #'+twitter/quit
|
||||
[remap +workspace/close-window-or-workspace] #'+twitter/quit)
|
||||
(when (featurep! :editor evil +everywhere)
|
||||
(define-key! twittering-mode-map
|
||||
[remap evil-window-delete] #'+twitter/quit
|
||||
"f" #'twittering-favorite
|
||||
"F" #'twittering-unfavorite
|
||||
"\C-f" #'twittering-follow
|
||||
"\C-F" #'twittering-unfollow
|
||||
"d" #'twittering-delete-status
|
||||
"r" #'twittering-retweet
|
||||
"R" #'twittering-toggle-or-retrieve-replied-statuses
|
||||
"o" #'twittering-update-status-interactive
|
||||
"O" #'+twitter/ace-link
|
||||
"/" #'twittering-search
|
||||
"J" #'twittering-goto-next-status
|
||||
"K" #'twittering-goto-previous-status
|
||||
"g" nil
|
||||
"gg" #'twittering-goto-first-status
|
||||
"G" #'twittering-goto-last-status
|
||||
"gj" #'twittering-goto-next-status-of-user
|
||||
"gk" #'twittering-goto-previous-status-of-user)))
|
|
@ -0,0 +1,5 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; app/twitter/packages.el
|
||||
|
||||
(package! twittering-mode :pin "114891e8fdb4f06b1326a6cf795e49c205cf9e29")
|
||||
(package! avy :pin "bbf1e7339eba06784dfe86643bb0fbddf5bb0342")
|
|
@ -0,0 +1,68 @@
|
|||
#+TITLE: checkers/grammar
|
||||
#+DATE: January 9, 2020
|
||||
#+SINCE: v3.0.0
|
||||
#+STARTUP: inlineimages nofold
|
||||
|
||||
* Table of Contents :TOC_3:noexport:
|
||||
- [[#description][Description]]
|
||||
- [[#maintainers][Maintainers]]
|
||||
- [[#module-flags][Module Flags]]
|
||||
- [[#plugins][Plugins]]
|
||||
- [[#prerequisites][Prerequisites]]
|
||||
- [[#features][Features]]
|
||||
- [[#language-tool][Language Tool]]
|
||||
- [[#commands][Commands]]
|
||||
- [[#writegood-mode][writegood-mode]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#troubleshooting][Troubleshooting]]
|
||||
|
||||
* Description
|
||||
This module adds grammar checking to Emacs to aid your writing by combining
|
||||
=lang-tool= and =writegood-mode=.
|
||||
|
||||
** Maintainers
|
||||
This module has no dedicated maintainers.
|
||||
|
||||
** Module Flags
|
||||
This module provides no flags.
|
||||
|
||||
** Plugins
|
||||
+ [[https://github.com/mhayashi1120/Emacs-langtool][langtool]]
|
||||
+ [[https://github.com/bnbeckwith/writegood-mode][writegood-mode]]
|
||||
|
||||
* Prerequisites
|
||||
This module requires langtool (which requires =Java 1.8+=).
|
||||
|
||||
It can be acquired either from https://languagetool.org/ or your OS's package
|
||||
manager:
|
||||
|
||||
+ macOS: ~brew install languagetool~
|
||||
+ Arch Linux: ~pacman -S languagetool~
|
||||
|
||||
This module tries to guess the location of languagetool-commandline.jar. If you
|
||||
get a warning that Doom =couldn't find languagetool-commandline.jar=, you will
|
||||
need to set ~langtool-language-tool-jar~ to its location.
|
||||
|
||||
* Features
|
||||
An in-depth list of features, how to use them, and their dependencies.
|
||||
|
||||
** Language Tool
|
||||
[[https://www.languagetool.org/][Language Tool]] is a polyglot proofreader service that checks for grammar and
|
||||
stylistic issues in your writing. This requires Java 1.8+.
|
||||
|
||||
#+begin_quote
|
||||
This requires Java 1.8+
|
||||
#+end_quote
|
||||
|
||||
*** Commands
|
||||
- ~langtool-check~
|
||||
- ~langtool-correct-buffer~
|
||||
|
||||
** writegood-mode
|
||||
This minor mode highlights weasel words, duplication and passive voice.
|
||||
|
||||
* Configuration
|
||||
How to configure this module, including common problems and how to address them.
|
||||
|
||||
* Troubleshooting
|
||||
Common issues and their solution, or places to look for help.
|
|
@ -0,0 +1,37 @@
|
|||
;;; checkers/grammar/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package! langtool
|
||||
:commands (langtool-check
|
||||
langtool-check-done
|
||||
langtool-show-message-at-point
|
||||
langtool-correct-buffer)
|
||||
:init (setq langtool-default-language "en-US")
|
||||
:config
|
||||
(unless (or langtool-bin
|
||||
langtool-language-tool-jar
|
||||
langtool-java-classpath)
|
||||
(cond (IS-MAC
|
||||
(cond
|
||||
;; is user using home brew?
|
||||
((file-directory-p "/usr/local/Cellar/languagetool")
|
||||
(setq langtool-language-tool-jar
|
||||
(locate-file "libexec/languagetool-commandline.jar"
|
||||
(doom-files-in "/usr/local/Cellar/languagetool"
|
||||
:type 'dirs
|
||||
:depth 2))))
|
||||
;; macports compatibility
|
||||
((file-directory-p "/opt/local/share/java/LanguageTool")
|
||||
(setq langtool-java-classpath "/opt/local/share/java/LanguageTool/*"))))
|
||||
(IS-LINUX
|
||||
(setq langtool-java-classpath "/usr/share/languagetool:/usr/share/java/languagetool/*")))))
|
||||
|
||||
|
||||
;; Detects weasel words, passive voice and duplicates. Proselint would be a
|
||||
;; better choice.
|
||||
(use-package! writegood-mode
|
||||
:hook (org-mode markdown-mode rst-mode asciidoc-mode latex-mode)
|
||||
:config
|
||||
(map! :localleader
|
||||
:map writegood-mode-map
|
||||
"g" #'writegood-grade-level
|
||||
"r" #'writegood-reading-ease))
|
|
@ -0,0 +1,5 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; checkers/grammar/packages.el
|
||||
|
||||
(package! langtool :pin "8276eccc5587bc12fd205ee58a7a982f0a136e41")
|
||||
(package! writegood-mode :pin "b71757ec337e226909fb0422f0224e31acc71733")
|
|
@ -0,0 +1,195 @@
|
|||
#+TITLE: checkers/spell
|
||||
#+DATE: January 9, 2020
|
||||
#+SINCE: v3.0.0
|
||||
#+STARTUP: inlineimages nofold
|
||||
|
||||
* Table of Contents :TOC_3:noexport:
|
||||
- [[#description][Description]]
|
||||
- [[#maintainers][Maintainers]]
|
||||
- [[#module-flags][Module Flags]]
|
||||
- [[#plugins][Plugins]]
|
||||
- [[#prerequisites][Prerequisites]]
|
||||
- [[#aspell][Aspell]]
|
||||
- [[#hunspell][Hunspell]]
|
||||
- [[#enchant][Enchant]]
|
||||
- [[#features][Features]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#changing-how-quickly-spell-fu-spellchecks-after-changes][Changing how quickly spell-fu spellchecks after changes]]
|
||||
- [[#spell-fu-users][Spell-fu users]]
|
||||
- [[#flyspell-users][Flyspell users]]
|
||||
- [[#reducing-false-positives-by-disabling-spelling-on-certain-faces][Reducing false positives by disabling spelling on certain faces]]
|
||||
- [[#spell-fu-users-1][Spell-fu users]]
|
||||
- [[#flyspell-users-1][Flyspell users]]
|
||||
- [[#adding-or-removing-words-to-your-personal-dictionary][Adding or removing words to your personal dictionary]]
|
||||
- [[#troubleshooting][Troubleshooting]]
|
||||
|
||||
* Description
|
||||
This modules provides spellchecking powered by =aspell=, =hunspell= or =enchant=.
|
||||
|
||||
Spellcheck is automatically loaded in many ~text-mode~ derivatives, which
|
||||
includes ~org-mode~, ~markdown-mode~, the Git Commit buffer (from magit),
|
||||
~mu4e-compose-mode~, and others.
|
||||
|
||||
** Maintainers
|
||||
This module has no dedicated maintainers.
|
||||
|
||||
** Module Flags
|
||||
+ =+flyspell= Use =flyspell= instead of =spell-fu=. It's significantly slower,
|
||||
but supports multiple languages and dictionaries.
|
||||
+ =+aspell= Use =aspell= as a backend for correcting words.
|
||||
+ =+hunspell= Use =hunspell= as a backend for correcting words.
|
||||
+ =+enchant= Use =enchant-2= as a backend for correcting words.
|
||||
+ =+everywhere= Spell check in programming modes as well (in comments).
|
||||
|
||||
** Plugins
|
||||
+ if =+flyspell=
|
||||
+ [[https://github.com/d12frosted/flyspell-correct][flyspell-correct]]
|
||||
+ [[https://github.com/d12frosted/flyspell-correct#flyspell-correct-ivy-interface][flyspell-correct-ivy]] (=completion/ivy=)
|
||||
+ [[https://github.com/d12frosted/flyspell-correct#flyspell-correct-helm-interface][flyspell-correct-helm]] (=completion/helm=)
|
||||
+ [[https://github.com/d12frosted/flyspell-correct#flyspell-correct-popup-interface][flyspell-correct-popup]] (if *neither* =completion/ivy= or =completion/helm=)
|
||||
+ [[https://github.com/rolandwalker/flyspell-lazy][flyspell-lazy]]
|
||||
+ else
|
||||
+ [[https://gitlab.com/ideasman42/emacs-spell-fu][spell-fu]]
|
||||
|
||||
* Prerequisites
|
||||
This module requires one of =aspell=, =hunspell= or =enchant-2=
|
||||
installed on your system and in your ~PATH~.
|
||||
They also need dictionaries for your language(s).
|
||||
|
||||
#+begin_quote
|
||||
If you *are not* using =+flyspell=, you will need aspell (and a dictionary)
|
||||
installed whether or not you have =+hunspell= or =+enchant= enabled.
|
||||
This is because =spell-fu= does not support generating the word list
|
||||
with anything other than =aspell= yet.
|
||||
#+end_quote
|
||||
|
||||
** Aspell
|
||||
+ Ubuntu: ~apt-get install aspell aspell-en~
|
||||
+ macOS: ~brew install aspell~
|
||||
+ Arch Linux: ~pacman -S aspell aspell-en~
|
||||
+ NixOS:
|
||||
#+BEGIN_SRC nix
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
(aspellWithDicts (dicts: with dicts; [ en en-computers en-science ]))
|
||||
];
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** Hunspell
|
||||
+ Ubuntu: ~apt-get install hunspell~
|
||||
+ macOS: ~brew install hunspell~
|
||||
+ Arch Linux: ~pacman -S hunspell~
|
||||
+ NixOS:
|
||||
#+BEGIN_SRC nix
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
hunspell
|
||||
];
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
** Enchant
|
||||
+ Ubuntu: ~apt-get install enchant-2~
|
||||
+ macOS: ~brew install enchant~
|
||||
+ Arch Linux: ~pacman -S enchant~
|
||||
+ NixOS:
|
||||
#+BEGIN_SRC nix
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
enchant
|
||||
];
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
Enchant is just a wrapper for other spelling libraries
|
||||
and you will need to have at least one of the supported backends installed as well.
|
||||
|
||||
* Features
|
||||
+ Spell checking and correction using =aspell=, =hunspell= or =enchant=.
|
||||
+ Ignores source code inside org or markdown files.
|
||||
+ Lazily spellchecking recent changes only when idle.
|
||||
+ Choosing suggestions using completion interfaces (=ivy= or =helm=).
|
||||
|
||||
When using =+everywhere=, spell checking is performed for as many major modes as
|
||||
possible, and not only ~text-mode~ derivatives. e.g. in comments for programming
|
||||
major modes.
|
||||
|
||||
* Configuration
|
||||
Dictionary is set by =ispell-dictionary= variable. Can be changed locally with
|
||||
the function =ispell-change-dictionary=.
|
||||
|
||||
** Changing how quickly spell-fu spellchecks after changes
|
||||
*** Spell-fu users
|
||||
Adjust ~spell-fu-idle-delay~ to change how long Emacs waits to spellcheck after
|
||||
recent changes.
|
||||
#+BEGIN_SRC elisp
|
||||
(after! spell-fu
|
||||
(setq spell-fu-idle-delay 0.5)) ; default is 0.25
|
||||
#+END_SRC
|
||||
|
||||
*** Flyspell users
|
||||
Lazy spellcheck is provided by =flyspell-lazy= package.
|
||||
|
||||
=flyspell-lazy-idle-seconds= sets how many idle seconds until spellchecking
|
||||
recent changes (default as 1), while =flyspell-lazy-window-idle-seconds= sets
|
||||
how many seconds until the whole window is spellchecked (default as 3).
|
||||
#+BEGIN_SRC elisp
|
||||
(after! flyspell
|
||||
(setq flyspell-lazy-idle-seconds 2))
|
||||
#+END_SRC
|
||||
|
||||
** Reducing false positives by disabling spelling on certain faces
|
||||
*** Spell-fu users
|
||||
Users can exclude what faces to preform spellchecking on by adjusting
|
||||
~+spell-excluded-faces-alist~ in a buffer-local hook:
|
||||
#+BEGIN_SRC elisp
|
||||
(setf (alist-get 'markdown-mode +spell-excluded-faces-alist)
|
||||
'(markdown-code-face
|
||||
markdown-reference-face
|
||||
markdown-link-face
|
||||
markdown-url-face
|
||||
markdown-markup-face
|
||||
markdown-html-attr-value-face
|
||||
markdown-html-attr-name-face
|
||||
markdown-html-tag-name-face))
|
||||
#+END_SRC
|
||||
|
||||
*** Flyspell users
|
||||
Flyspell will run a series of predicate functions to determine if a word should be spell checked. You can add your own with ~set-flyspell-predicate!~:
|
||||
#+BEGIN_SRC elisp
|
||||
(set-flyspell-predicate! '(markdown-mode gfm-mode)
|
||||
#'+markdown-flyspell-word-p)
|
||||
#+END_SRC
|
||||
|
||||
Flyspell predicates take no arguments and must return a boolean to determine if
|
||||
the word at point should be spell checked. For example:
|
||||
#+BEGIN_SRC elisp
|
||||
(defun +markdown-flyspell-word-p ()
|
||||
"Return t if point is on a word that should be spell checked.
|
||||
|
||||
Return nil if on a link url, markup, html, or references."
|
||||
(let ((faces (doom-enlist (get-text-property (point) 'face))))
|
||||
(or (and (memq 'font-lock-comment-face faces)
|
||||
(memq 'markdown-code-face faces))
|
||||
(not (cl-loop with unsafe-faces = '(markdown-reference-face
|
||||
markdown-url-face
|
||||
markdown-markup-face
|
||||
markdown-comment-face
|
||||
markdown-html-attr-name-face
|
||||
markdown-html-attr-value-face
|
||||
markdown-html-tag-name-face
|
||||
markdown-code-face)
|
||||
for face in faces
|
||||
if (memq face unsafe-faces)
|
||||
return t)))))
|
||||
#+END_SRC
|
||||
|
||||
** Adding or removing words to your personal dictionary
|
||||
Use ~M-x +spell/add-word~ and ~M-x +spell/remove-word~ to whitelist words that
|
||||
you know are not misspellings. For evil users these are bound to =zg= and =zw=,
|
||||
respectively. =+flyspell= users can also add/remove words from the
|
||||
~flyspell-correct~ popup interface (there will be extra options on the list of
|
||||
corrections for "save word to dictionary").
|
||||
|
||||
* TODO Troubleshooting
|
|
@ -0,0 +1,86 @@
|
|||
;;; checkers/spell/autoload/+flyspell.el -*- lexical-binding: t; -*-
|
||||
;;;###if (featurep! +flyspell)
|
||||
|
||||
;;;###autodef
|
||||
(defalias 'flyspell-mode! #'flyspell-mode)
|
||||
|
||||
(defvar +spell--flyspell-predicate-alist nil
|
||||
"TODO")
|
||||
|
||||
;;;###autodef
|
||||
(defun set-flyspell-predicate! (modes predicate)
|
||||
"TODO"
|
||||
(declare (indent defun))
|
||||
(dolist (mode (doom-enlist modes) +spell--flyspell-predicate-alist)
|
||||
(add-to-list '+spell--flyspell-predicate-alist (cons mode predicate))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +spell-init-flyspell-predicate-h ()
|
||||
"TODO"
|
||||
(when-let (pred (assq major-mode +spell--flyspell-predicate-alist))
|
||||
(setq-local flyspell-generic-check-word-predicate (cdr pred))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +spell-correction-at-point-p (&optional point)
|
||||
"TODO"
|
||||
(cl-loop for ov in (overlays-at (or point (point)))
|
||||
if (overlay-get ov 'flyspell-overlay)
|
||||
return t))
|
||||
|
||||
;;;###autoload
|
||||
(defun +spell/add-word (word &optional scope)
|
||||
"Add WORD to your personal dictionary, within SCOPE.
|
||||
|
||||
SCOPE can be `buffer' or `session' to exclude words only from the current buffer
|
||||
or session. Otherwise, the addition is permanent."
|
||||
(interactive
|
||||
(list (progn (require 'flyspell)
|
||||
(car (flyspell-get-word)))
|
||||
(cond ((equal current-prefix-arg '(16))
|
||||
'session)
|
||||
((equal current-prefix-arg '(4))
|
||||
'buffer))))
|
||||
(require 'flyspell)
|
||||
(cond
|
||||
((null scope)
|
||||
(ispell-send-string (concat "*" word "\n"))
|
||||
(ispell-send-string "#\n")
|
||||
(flyspell-unhighlight-at (point))
|
||||
(setq ispell-pdict-modified-p '(t)))
|
||||
((memq scope '(buffer session))
|
||||
(ispell-send-string (concat "@" word "\n"))
|
||||
(add-to-list 'ispell-buffer-session-localwords word)
|
||||
(or ispell-buffer-local-name ; session localwords might conflict
|
||||
(setq ispell-buffer-local-name (buffer-name)))
|
||||
(flyspell-unhighlight-at (point))
|
||||
(if (null ispell-pdict-modified-p)
|
||||
(setq ispell-pdict-modified-p
|
||||
(list ispell-pdict-modified-p)))
|
||||
(if (eq replace 'buffer)
|
||||
(ispell-add-per-file-word-list word))))
|
||||
(ispell-pdict-save t))
|
||||
|
||||
;;;###autoload
|
||||
(defun +spell/remove-word (word &optional _scope)
|
||||
"Remove WORD from your personal dictionary."
|
||||
(interactive)
|
||||
(user-error "Not supported yet with +flyspell"))
|
||||
|
||||
;;;###autoload
|
||||
(defun +spell/next-error ()
|
||||
"Jump to next flyspell error."
|
||||
(interactive)
|
||||
(call-interactively
|
||||
(if (featurep 'evil)
|
||||
#'evil-next-flyspell-error
|
||||
#'flyspell-goto-next-error)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +spell/previous-error ()
|
||||
"Jump to previous flyspell error."
|
||||
(interactive)
|
||||
(call-interactively
|
||||
(if (featurep 'evil)
|
||||
#'evil-prev-flyspell-error
|
||||
;; TODO Implement this
|
||||
(user-error "Not supported"))))
|
|
@ -0,0 +1,115 @@
|
|||
;;; checkers/spell/autoload/+spell-fu.el -*- lexical-binding: t; -*-
|
||||
;;;###if (not (featurep! +flyspell))
|
||||
|
||||
(defun +spell--correct (replace poss word orig-pt start end)
|
||||
(cond ((eq replace 'ignore)
|
||||
(goto-char orig-pt)
|
||||
nil)
|
||||
((eq replace 'save)
|
||||
(goto-char orig-pt)
|
||||
(ispell-send-string (concat "*" word "\n"))
|
||||
(ispell-send-string "#\n")
|
||||
(setq ispell-pdict-modified-p '(t)))
|
||||
((or (eq replace 'buffer) (eq replace 'session))
|
||||
(ispell-send-string (concat "@" word "\n"))
|
||||
(add-to-list 'ispell-buffer-session-localwords word)
|
||||
(or ispell-buffer-local-name ; session localwords might conflict
|
||||
(setq ispell-buffer-local-name (buffer-name)))
|
||||
(if (null ispell-pdict-modified-p)
|
||||
(setq ispell-pdict-modified-p
|
||||
(list ispell-pdict-modified-p)))
|
||||
(goto-char orig-pt)
|
||||
(if (eq replace 'buffer)
|
||||
(ispell-add-per-file-word-list word)))
|
||||
(replace
|
||||
(let ((new-word (if (atom replace)
|
||||
replace
|
||||
(car replace)))
|
||||
(orig-pt (+ (- (length word) (- end start))
|
||||
orig-pt)))
|
||||
(unless (equal new-word (car poss))
|
||||
(delete-region start end)
|
||||
(goto-char start)
|
||||
(insert new-word))))
|
||||
((goto-char orig-pt)
|
||||
nil)))
|
||||
|
||||
(defun +spell-correct-ivy-fn (candidates word)
|
||||
(ivy-read (format "Corrections for %S: " word) candidates))
|
||||
|
||||
(defun +spell-correct-helm-fn (candidates word)
|
||||
(helm :sources (helm-build-sync-source
|
||||
"Ispell"
|
||||
:candidates candidates)
|
||||
:prompt (format "Corrections for %S: " word)))
|
||||
|
||||
(defun +spell-correct-generic-fn (candidates word)
|
||||
(completing-read (format "Corrections for %S: " word) candidates))
|
||||
|
||||
;;;###autoload
|
||||
(defun +spell/correct ()
|
||||
"Correct spelling of word at point."
|
||||
(interactive)
|
||||
;; spell-fu fails to initialize correctly if it can't find aspell or a similar
|
||||
;; program. We want to signal the error, not tell the user that every word is
|
||||
;; spelled correctly.
|
||||
(unless (;; This is what spell-fu uses to check for the aspell executable
|
||||
or (and ispell-really-aspell ispell-program-name)
|
||||
(executable-find "aspell"))
|
||||
(user-error "Aspell is required for spell checking"))
|
||||
|
||||
(ispell-set-spellchecker-params)
|
||||
(save-current-buffer
|
||||
(ispell-accept-buffer-local-defs))
|
||||
(if (not (or (featurep! :completion ivy)
|
||||
(featurep! :completion helm)))
|
||||
(call-interactively #'ispell-word)
|
||||
(cl-destructuring-bind (start . end)
|
||||
(or (bounds-of-thing-at-point 'word)
|
||||
(user-error "No word at point"))
|
||||
(let ((word (thing-at-point 'word t))
|
||||
(orig-pt (point))
|
||||
poss ispell-filter)
|
||||
(ispell-send-string "%\n")
|
||||
(ispell-send-string (concat "^" word "\n"))
|
||||
(while (progn (accept-process-output ispell-process)
|
||||
(not (string= "" (car ispell-filter)))))
|
||||
;; Remove leading empty element
|
||||
(setq ispell-filter (cdr ispell-filter))
|
||||
;; ispell process should return something after word is sent. Tag word as
|
||||
;; valid (i.e., skip) otherwise
|
||||
(unless ispell-filter
|
||||
(setq ispell-filter '(*)))
|
||||
(when (consp ispell-filter)
|
||||
(setq poss (ispell-parse-output (car ispell-filter))))
|
||||
(cond
|
||||
((or (eq poss t) (stringp poss))
|
||||
;; don't correct word
|
||||
(message "%s is correct" (funcall ispell-format-word-function word))
|
||||
t)
|
||||
((null poss)
|
||||
;; ispell error
|
||||
(error "Ispell: error in Ispell process"))
|
||||
(t
|
||||
;; The word is incorrect, we have to propose a replacement.
|
||||
(setq res (funcall +spell-correct-interface (nth 2 poss) word))
|
||||
;; Some interfaces actually eat 'C-g' so it's impossible to stop rapid
|
||||
;; mode. So when interface returns nil we treat it as a stop.
|
||||
(unless res (setq res (cons 'break word)))
|
||||
(cond
|
||||
((stringp res)
|
||||
(+spell--correct res poss word orig-pt start end))
|
||||
((let ((cmd (car res))
|
||||
(wrd (cdr res)))
|
||||
(unless (or (eq cmd 'skip)
|
||||
(eq cmd 'break)
|
||||
(eq cmd 'stop))
|
||||
(+spell--correct cmd poss wrd orig-pt start end)
|
||||
(unless (string-equal wrd word)
|
||||
(+spell--correct wrd poss word orig-pt start end))))))
|
||||
(ispell-pdict-save t)))))))
|
||||
|
||||
;;;###autoload (defalias '+spell/add-word #'spell-fu-word-add)
|
||||
;;;###autoload (defalias '+spell/remove-word #'spell-fu-word-remove)
|
||||
;;;###autoload (defalias '+spell/next-error #'spell-fu-goto-next-error)
|
||||
;;;###autoload (defalias '+spell/previous-error #'spell-fu-goto-previous-error)
|
|
@ -0,0 +1,230 @@
|
|||
;;; checkers/spell/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Ispell
|
||||
|
||||
;; `elisp-mode' is loaded at startup. In order to lazy load its config we need
|
||||
;; to pretend it isn't loaded
|
||||
(delq! 'ispell features)
|
||||
|
||||
(global-set-key [remap ispell-word] #'+spell/correct)
|
||||
|
||||
(after! ispell
|
||||
;; Don't spellcheck org blocks
|
||||
(pushnew! ispell-skip-region-alist
|
||||
'(":\\(PROPERTIES\\|LOGBOOK\\):" . ":END:")
|
||||
'("#\\+BEGIN_SRC" . "#\\+END_SRC")
|
||||
'("#\\+BEGIN_EXAMPLE" . "#\\+END_EXAMPLE"))
|
||||
|
||||
;; Enable either aspell, hunspell or enchant.
|
||||
;; If no module flags are given, enable either aspell, hunspell or enchant
|
||||
;; if their binary is found.
|
||||
;; If one of the flags `+aspell', `+hunspell' or `+enchant' is given,
|
||||
;; only enable that spell checker.
|
||||
(pcase (cond ((featurep! +aspell) 'aspell)
|
||||
((featurep! +hunspell) 'hunspell)
|
||||
((featurep! +enchant) 'enchant)
|
||||
((executable-find "aspell") 'aspell)
|
||||
((executable-find "hunspell") 'hunspell)
|
||||
((executable-find "enchant-2") 'enchant))
|
||||
(`aspell
|
||||
(setq ispell-program-name "aspell"
|
||||
ispell-extra-args '("--sug-mode=ultra"
|
||||
"--run-together"))
|
||||
|
||||
(unless ispell-aspell-dict-dir
|
||||
(setq ispell-aspell-dict-dir
|
||||
(ispell-get-aspell-config-value "dict-dir")))
|
||||
(unless ispell-aspell-data-dir
|
||||
(setq ispell-aspell-data-dir
|
||||
(ispell-get-aspell-config-value "data-dir")))
|
||||
(unless ispell-personal-dictionary
|
||||
(setq ispell-personal-dictionary
|
||||
(expand-file-name (concat "ispell/" ispell-dictionary ".pws")
|
||||
doom-etc-dir)))
|
||||
|
||||
(add-hook! 'text-mode-hook
|
||||
(defun +spell-remove-run-together-switch-for-aspell-h ()
|
||||
(setq-local ispell-extra-args (remove "--run-together" ispell-extra-args))))
|
||||
|
||||
(defun +spell-init-ispell-extra-args-a (orig-fun &rest args)
|
||||
:around '(ispell-word flyspell-auto-correct-word)
|
||||
(let ((ispell-extra-args (remove "--run-together" ispell-extra-args)))
|
||||
(ispell-kill-ispell t)
|
||||
(apply orig-fun args)
|
||||
(ispell-kill-ispell t))))
|
||||
|
||||
(`hunspell
|
||||
(setq ispell-program-name "hunspell"))
|
||||
|
||||
(`enchant
|
||||
(setq ispell-program-name "enchant-2"))
|
||||
|
||||
(_ (doom-log "Spell checker not found. Either install `aspell', `hunspell' or `enchant'"))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Implementations
|
||||
|
||||
(eval-if! (not (featurep! +flyspell))
|
||||
|
||||
(use-package! spell-fu
|
||||
:when (executable-find "aspell")
|
||||
:hook (text-mode . spell-fu-mode)
|
||||
:general ([remap ispell-word] #'+spell/correct)
|
||||
:preface
|
||||
(defvar +spell-correct-interface
|
||||
(cond ((featurep! :completion ivy)
|
||||
#'+spell-correct-ivy-fn)
|
||||
((featurep! :completion helm)
|
||||
#'+spell-correct-helm-fn)
|
||||
(#'+spell-correct-generic-fn))
|
||||
"Function to use to display corrections.")
|
||||
|
||||
:init
|
||||
(defvar +spell-excluded-faces-alist
|
||||
'((markdown-mode
|
||||
. (markdown-code-face
|
||||
markdown-html-attr-name-face
|
||||
markdown-html-attr-value-face
|
||||
markdown-html-tag-name-face
|
||||
markdown-link-face
|
||||
markdown-markup-face
|
||||
markdown-reference-face
|
||||
markdown-url-face))
|
||||
(org-mode
|
||||
. (org-block
|
||||
org-block-begin-line
|
||||
org-block-end-line
|
||||
org-code
|
||||
org-date
|
||||
org-formula
|
||||
org-latex-and-related
|
||||
org-link
|
||||
org-meta-line
|
||||
org-property-value
|
||||
org-ref-cite-face
|
||||
org-special-keyword
|
||||
org-tag
|
||||
org-todo
|
||||
org-todo-keyword-done
|
||||
org-todo-keyword-habt
|
||||
org-todo-keyword-kill
|
||||
org-todo-keyword-outd
|
||||
org-todo-keyword-todo
|
||||
org-todo-keyword-wait
|
||||
org-verbatim))
|
||||
(latex-mode
|
||||
. (font-latex-math-face
|
||||
font-latex-sedate-face
|
||||
font-lock-function-name-face
|
||||
font-lock-keyword-face
|
||||
font-lock-variable-name-face)))
|
||||
"Faces in certain major modes that spell-fu will not spellcheck.")
|
||||
|
||||
(setq spell-fu-directory (concat doom-etc-dir "spell-fu"))
|
||||
(when (featurep! +everywhere)
|
||||
(add-hook! '(yaml-mode-hook
|
||||
conf-mode-hook
|
||||
prog-mode-hook)
|
||||
#'spell-fu-mode))
|
||||
:config
|
||||
(map! :after spell-fu
|
||||
:map override
|
||||
:n [return]
|
||||
(cmds! (memq 'spell-fu-incorrect-face (face-at-point nil t))
|
||||
#'+spell/correct))
|
||||
|
||||
(defadvice! +spell--create-word-dict-a (_word words-file _action)
|
||||
:before #'spell-fu--word-add-or-remove
|
||||
(unless (file-exists-p words-file)
|
||||
(make-directory (file-name-directory words-file) t)
|
||||
(with-temp-file words-file
|
||||
(insert (format "personal_ws-1.1 %s 0\n" ispell-dictionary)))))
|
||||
|
||||
(add-hook! 'spell-fu-mode-hook
|
||||
(defun +spell-init-excluded-faces-h ()
|
||||
"Set `spell-fu-faces-exclude' according to `+spell-excluded-faces-alist'."
|
||||
(when-let (excluded (cdr (cl-find-if #'derived-mode-p +spell-excluded-faces-alist :key #'car)))
|
||||
(setq-local spell-fu-faces-exclude excluded))))
|
||||
|
||||
;; TODO custom `spell-fu-check-range' function to reduce false positives
|
||||
;; more intelligently, or modify `spell-fu-word-regexp' to include
|
||||
;; non-latin charactersets.
|
||||
)
|
||||
|
||||
(use-package! flyspell ; built-in
|
||||
:defer t
|
||||
:preface
|
||||
;; `flyspell' is loaded at startup. In order to lazy load its config we need
|
||||
;; to pretend it isn't loaded.
|
||||
(defer-feature! flyspell flyspell-mode flyspell-prog-mode)
|
||||
:init
|
||||
(add-hook! '(org-mode-hook
|
||||
markdown-mode-hook
|
||||
TeX-mode-hook
|
||||
rst-mode-hook
|
||||
mu4e-compose-mode-hook
|
||||
message-mode-hook
|
||||
git-commit-mode-hook)
|
||||
#'flyspell-mode)
|
||||
|
||||
(when (featurep! +everywhere)
|
||||
(add-hook! '(yaml-mode-hook
|
||||
conf-mode-hook
|
||||
prog-mode-hook)
|
||||
#'flyspell-prog-mode))
|
||||
|
||||
:config
|
||||
(provide 'ispell) ; forcibly load ispell configs
|
||||
|
||||
(setq flyspell-issue-welcome-flag nil
|
||||
;; Significantly speeds up flyspell, which would otherwise print
|
||||
;; messages for every word when checking the entire buffer
|
||||
flyspell-issue-message-flag nil)
|
||||
|
||||
(add-hook! 'flyspell-mode-hook
|
||||
(defun +spell-inhibit-duplicate-detection-maybe-h ()
|
||||
"Don't mark duplicates when style/grammar linters are present.
|
||||
e.g. proselint and langtool."
|
||||
(and (or (and (bound-and-true-p flycheck-mode)
|
||||
(executable-find "proselint"))
|
||||
(featurep 'langtool))
|
||||
(setq-local flyspell-mark-duplications-flag nil))))
|
||||
|
||||
;; Ensure mode-local predicates declared with `set-flyspell-predicate!' are
|
||||
;; used in their respective major modes.
|
||||
(add-hook 'flyspell-mode-hook #'+spell-init-flyspell-predicate-h)
|
||||
|
||||
(let ((flyspell-correct
|
||||
(cmds! (and (not mark-active)
|
||||
(not (and (bound-and-true-p evil-local-mode)
|
||||
(or (evil-insert-state-p)
|
||||
(evil-emacs-state-p))))
|
||||
(memq 'flyspell-incorrect (face-at-point nil t)))
|
||||
#'flyspell-correct-at-point)))
|
||||
(map! :map flyspell-mouse-map
|
||||
"RET" flyspell-correct
|
||||
[return] flyspell-correct
|
||||
[mouse-1] #'flyspell-correct-at-point)))
|
||||
|
||||
|
||||
(use-package! flyspell-correct
|
||||
:commands flyspell-correct-previous
|
||||
:general ([remap ispell-word] #'flyspell-correct-at-point)
|
||||
:config
|
||||
(cond ((and (featurep! :completion helm)
|
||||
(require 'flyspell-correct-helm nil t)))
|
||||
((and (featurep! :completion ivy)
|
||||
(require 'flyspell-correct-ivy nil t)))
|
||||
((require 'flyspell-correct-popup nil t)
|
||||
(setq flyspell-popup-correct-delay 0.8)
|
||||
(define-key popup-menu-keymap [escape] #'keyboard-quit))))
|
||||
|
||||
|
||||
(use-package! flyspell-lazy
|
||||
:after flyspell
|
||||
:config
|
||||
(setq flyspell-lazy-idle-seconds 1
|
||||
flyspell-lazy-window-idle-seconds 3)
|
||||
(flyspell-lazy-mode +1)))
|
|
@ -0,0 +1,10 @@
|
|||
;;; checkers/spell/doctor.el -*- lexical-binding: t; -*-
|
||||
|
||||
(when (or (not (featurep! +flyspell))
|
||||
(featurep! +aspell))
|
||||
(unless (executable-find "aspell")
|
||||
(warn! "Couldn't find aspell executable; spell checker will not work")))
|
||||
|
||||
(when (featurep! +hunspell)
|
||||
(unless (executable-find "hunspell")
|
||||
(warn! "Couldn't find hunspell executable; spell checker will not work")))
|
|
@ -0,0 +1,12 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; checkers/spell/packages.el
|
||||
|
||||
(if (not (featurep! +flyspell))
|
||||
(package! spell-fu :pin "a7db58747131dca2eee0e0757c3d254d391ddd1c")
|
||||
(package! flyspell-correct :pin "6d603a1dc51918f7f8aaf99dd5443f74a0afc794")
|
||||
(cond ((featurep! :completion ivy)
|
||||
(package! flyspell-correct-ivy))
|
||||
((featurep! :completion helm)
|
||||
(package! flyspell-correct-helm))
|
||||
((package! flyspell-correct-popup)))
|
||||
(package! flyspell-lazy :pin "3ebf68cc9eb10c972a2de8d7861cbabbbce69570"))
|
|
@ -0,0 +1,25 @@
|
|||
;;; checkers/syntax/autoload.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autodef
|
||||
(defun set-next-checker! (mode checker next &optional append)
|
||||
"TODO"
|
||||
(let ((fn (intern (format "+syntax--init-checkers-for-%s-h" mode))))
|
||||
(fset fn
|
||||
(lambda ()
|
||||
(if (not (bound-and-true-p flycheck-mode))
|
||||
(add-hook 'flycheck-mode-hook fn 'append 'local)
|
||||
(flycheck-add-next-checker checker next append)
|
||||
(remove-hook 'flycheck-mode-hook fn 'local))))
|
||||
(add-hook (intern (format "%s-hook" mode)) fn)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +syntax-init-popups-h ()
|
||||
"Activate `flycheck-posframe-mode' if available and in GUI Emacs.
|
||||
Activate `flycheck-popup-tip-mode' otherwise.
|
||||
Do nothing if `lsp-ui-mode' is active and `lsp-ui-sideline-enable' is non-nil."
|
||||
(unless (and (bound-and-true-p lsp-ui-mode)
|
||||
lsp-ui-sideline-enable)
|
||||
(if (and (fboundp 'flycheck-posframe-mode)
|
||||
(display-graphic-p))
|
||||
(flycheck-posframe-mode +1)
|
||||
(flycheck-popup-tip-mode +1))))
|
|
@ -0,0 +1,83 @@
|
|||
;;; checkers/syntax/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;
|
||||
;;; Flycheck
|
||||
|
||||
(use-package! flycheck
|
||||
:commands flycheck-list-errors flycheck-buffer
|
||||
:hook (doom-first-buffer . global-flycheck-mode)
|
||||
:config
|
||||
(setq flycheck-emacs-lisp-load-path 'inherit)
|
||||
|
||||
;; Rerunning checks on every newline is a mote excessive.
|
||||
(delq 'new-line flycheck-check-syntax-automatically)
|
||||
;; And don't recheck on idle as often
|
||||
(setq flycheck-idle-change-delay 1.0)
|
||||
|
||||
;; For the above functionality, check syntax in a buffer that you switched to
|
||||
;; only briefly. This allows "refreshing" the syntax check state for several
|
||||
;; buffers quickly after e.g. changing a config file.
|
||||
(setq flycheck-buffer-switch-check-intermediate-buffers t)
|
||||
|
||||
;; Display errors a little quicker (default is 0.9s)
|
||||
(setq flycheck-display-errors-delay 0.25)
|
||||
|
||||
;; Don't commandeer input focus if the error message pops up (happens when
|
||||
;; tooltips and childframes are disabled).
|
||||
(set-popup-rules!
|
||||
'(("^\\*Flycheck error messages\\*" :select nil)
|
||||
("^\\*Flycheck errors\\*" :size 0.25)))
|
||||
|
||||
(add-hook! 'doom-escape-hook :append
|
||||
(defun +syntax-check-buffer-h ()
|
||||
"Flycheck buffer on ESC in normal mode."
|
||||
(when flycheck-mode
|
||||
(ignore-errors (flycheck-buffer))
|
||||
nil)))
|
||||
|
||||
(map! :map flycheck-error-list-mode-map
|
||||
:n "C-n" #'flycheck-error-list-next-error
|
||||
:n "C-p" #'flycheck-error-list-previous-error
|
||||
:n "j" #'flycheck-error-list-next-error
|
||||
:n "k" #'flycheck-error-list-previous-error
|
||||
:n "RET" #'flycheck-error-list-goto-error
|
||||
:n [return] #'flycheck-error-list-goto-error))
|
||||
|
||||
|
||||
(use-package! flycheck-popup-tip
|
||||
:commands flycheck-popup-tip-show-popup flycheck-popup-tip-delete-popup
|
||||
:hook (flycheck-mode . +syntax-init-popups-h)
|
||||
:config
|
||||
(setq flycheck-popup-tip-error-prefix "X ")
|
||||
(after! evil
|
||||
;; Don't display popups while in insert or replace mode, as it can affect
|
||||
;; the cursor's position or cause disruptive input delays.
|
||||
(add-hook! '(evil-insert-state-entry-hook evil-replace-state-entry-hook)
|
||||
#'flycheck-popup-tip-delete-popup)
|
||||
(defadvice! +syntax--disable-flycheck-popup-tip-maybe-a (&rest _)
|
||||
:before-while #'flycheck-popup-tip-show-popup
|
||||
(if evil-local-mode
|
||||
(eq evil-state 'normal)
|
||||
(not (bound-and-true-p company-backend))))))
|
||||
|
||||
|
||||
(use-package! flycheck-posframe
|
||||
:when (featurep! +childframe)
|
||||
:hook (flycheck-mode . +syntax-init-popups-h)
|
||||
:config
|
||||
(setq flycheck-posframe-warning-prefix "! "
|
||||
flycheck-posframe-info-prefix "··· "
|
||||
flycheck-posframe-error-prefix "X ")
|
||||
(after! company
|
||||
;; Don't display popups if company is open
|
||||
(add-hook 'flycheck-posframe-inhibit-functions #'company--active-p))
|
||||
(after! evil
|
||||
;; Don't display popups while in insert or replace mode, as it can affect
|
||||
;; the cursor's position or cause disruptive input delays.
|
||||
(add-hook! 'flycheck-posframe-inhibit-functions
|
||||
#'evil-insert-state-p
|
||||
#'evil-replace-state-p)))
|
||||
|
||||
|
||||
;;
|
||||
;;; TODO Flymake
|
|
@ -0,0 +1,9 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; checkers/syntax/packages.el
|
||||
|
||||
(package! flycheck :pin "79c9245ee0bd1722d41c865fef69aa2b1ac08fde")
|
||||
(package! flycheck-popup-tip :pin "ef86aad907f27ca076859d8d9416f4f7727619c6")
|
||||
(when (featurep! +childframe)
|
||||
(package! flycheck-posframe :pin "66b73ddb93b357fe9b849d2aa14d5cc9e89e9ffd"))
|
||||
|
||||
;; TODO flymake?
|
|
@ -0,0 +1,130 @@
|
|||
#+TITLE: completion/company
|
||||
#+DATE: February 19, 2017
|
||||
#+SINCE: v2.0
|
||||
#+STARTUP: inlineimages
|
||||
|
||||
* Table of Contents :TOC_3:noexport:
|
||||
- [[#description][Description]]
|
||||
- [[#module-flags][Module Flags]]
|
||||
- [[#plugins][Plugins]]
|
||||
- [[#prerequisites][Prerequisites]]
|
||||
- [[#features][Features]]
|
||||
- [[#code-completion][Code completion]]
|
||||
- [[#vim-esque-omni-completion-prefix-c-x][Vim-esque omni-completion prefix (C-x)]]
|
||||
- [[#configuration][Configuration]]
|
||||
- [[#enable-company-backends-in-certain-modes][Enable company backend(s) in certain modes]]
|
||||
- [[#troubleshooting][Troubleshooting]]
|
||||
- [[#x-mode-doesnt-have-code-completion-support-or-requires-extra-setup][X-mode doesn't have code completion support or requires extra setup.]]
|
||||
- [[#no-backends-or-the-incorrect-ones-have-been-registered-for-x-mode][No backends (or the incorrect ones) have been registered for X-mode.]]
|
||||
|
||||
* Description
|
||||
This module provides code completion, powered by [[https://github.com/company-mode/company-mode][company-mode]]. It is required
|
||||
for code completion in many of Doom's :lang modules.
|
||||
|
||||
https://assets.doomemacs.org/completion/company/overlay.png
|
||||
|
||||
** Module Flags
|
||||
+ =+childframe= Enables displaying completion candidates in a child frame,
|
||||
rather than an overlay or tooltip (among with other UI enhancements). *This
|
||||
requires GUI Emacs 26.1+ and is incompatible with the =+tng= flag*
|
||||
+ =+tng= Enables completion using only ~TAB~. Pressing ~TAB~ will select the
|
||||
next completion suggestion, while ~S-TAB~ will select the previous one. *This
|
||||
is incompatible with the =+childframe= flag*
|
||||
|
||||
** Plugins
|
||||
+ [[https://github.com/company-mode/company-mode][company-mode]]
|
||||
+ [[https://github.com/hlissner/emacs-company-dict][company-dict]]
|
||||
+ [[https://github.com/sebastiencs/company-box][company-box]]* (=+childframe=)
|
||||
|
||||
* Prerequisites
|
||||
This module has no direct prerequisites.
|
||||
|
||||
However, some major modes may require additional setup for code completion to
|
||||
work in them. Some major modes may have no completion support at all. Check that
|
||||
major mode's module's documentation for details.
|
||||
|
||||
* Features
|
||||
** Code completion
|
||||
By default, completion is triggered after a short idle period or with the
|
||||
=C-SPC= key. While the popup is visible, the following keys are available:
|
||||
|
||||
| Keybind | Description |
|
||||
|---------+------------------------------------------|
|
||||
| =C-n= | Go to next candidate |
|
||||
| =C-p= | Go to previous candidate |
|
||||
| =C-j= | (evil) Go to next candidate |
|
||||
| =C-k= | (evil) Go to previous candidate |
|
||||
| =C-h= | Display documentation (if available) |
|
||||
| =C-u= | Move to previous page of candidates |
|
||||
| =C-d= | Move to next page of candidates |
|
||||
| =C-s= | Filter candidates |
|
||||
| =C-S-s= | Search candidates with helm/ivy |
|
||||
| =C-SPC= | Complete common |
|
||||
| =TAB= | Complete common or select next candidate |
|
||||
| =S-TAB= | Select previous candidate |
|
||||
|
||||
** Vim-esque omni-completion prefix (C-x)
|
||||
In the spirit of Vim's omni-completion, the following insert mode keybinds are
|
||||
available to evil users to access specific company backends:
|
||||
|
||||
| Keybind | Description |
|
||||
|-----------+-----------------------------------|
|
||||
| =C-x C-]= | Complete etags |
|
||||
| =C-x C-f= | Complete file path |
|
||||
| =C-x C-k= | Complete from dictionary/keyword |
|
||||
| =C-x C-l= | Complete full line |
|
||||
| =C-x C-o= | Invoke complete-at-point function |
|
||||
| =C-x C-n= | Complete next symbol at point |
|
||||
| =C-x C-p= | Complete previous symbol at point |
|
||||
| =C-x C-s= | Complete snippet |
|
||||
| =C-x s= | Complete spelling suggestions |
|
||||
|
||||
* Configuration
|
||||
** Enable company backend(s) in certain modes
|
||||
The ~set-company-backend!~ function exists for setting ~company-backends~
|
||||
buffer-locally in MODES, which is either a major-mode symbol, a minor-mode
|
||||
symbol, or a list of either. BACKENDS are prepended to ~company-backends~ for
|
||||
those modes.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(after! js2-mode
|
||||
(set-company-backend! 'js2-mode 'company-tide 'company-yasnippet))
|
||||
|
||||
(after! sh-script
|
||||
(set-company-backend! 'sh-mode
|
||||
'(company-shell :with company-yasnippet)))
|
||||
|
||||
(after! cc-mode
|
||||
(set-company-backend! 'c-mode
|
||||
'(:separate company-irony-c-headers company-irony)))
|
||||
#+END_SRC
|
||||
|
||||
To unset the backends for a particular mode, pass ~nil~ to it:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(after! sh-script
|
||||
(set-company-backend! 'sh-mode nil))
|
||||
#+END_SRC
|
||||
|
||||
* Troubleshooting
|
||||
If code completion isn't working for you, consider the following common causes
|
||||
before you file a bug report:
|
||||
|
||||
** X-mode doesn't have code completion support or requires extra setup.
|
||||
There is no guarantee your language mode will have completion support.
|
||||
|
||||
Some, like ~lua-mode~, don't have completion support in Emacs at all. Others may
|
||||
requires additional setup to get code completion working. For instance,
|
||||
~go-mode~ requires ~guru~ to be installed on your system, and ~ruby-mode~
|
||||
requires that you have a Robe server running (~M-x robe-start~).
|
||||
|
||||
Check the relevant module's documentation for this kind of information.
|
||||
|
||||
** No backends (or the incorrect ones) have been registered for X-mode.
|
||||
Doom expects every mode to have an explicit list of company-backends (and as
|
||||
short a list as possible). This may mean you aren't getting all the completion
|
||||
you want or any at all.
|
||||
|
||||
Check the value of ~company-backends~ (=SPC h v company-backends=) from that
|
||||
mode to see what backends are available. Check the [[*Assigning company backend(s) to modes][Configuration section]] for
|
||||
details on changing what backends are available for that mode.
|
|
@ -0,0 +1,155 @@
|
|||
;;; completion/company/autoload.el -*- lexical-binding: t; -*-
|
||||
|
||||
;;;###autoload
|
||||
(defvar +company-backend-alist
|
||||
'((text-mode (:separate company-dabbrev company-yasnippet company-ispell))
|
||||
(prog-mode company-capf company-yasnippet)
|
||||
(conf-mode company-capf company-dabbrev-code company-yasnippet))
|
||||
"An alist matching modes to company backends. The backends for any mode is
|
||||
built from this.")
|
||||
|
||||
;;;###autodef
|
||||
(defun set-company-backend! (modes &rest backends)
|
||||
"Prepends BACKENDS (in order) to `company-backends' in MODES.
|
||||
|
||||
MODES should be one symbol or a list of them, representing major or minor modes.
|
||||
This will overwrite backends for MODES on consecutive uses.
|
||||
|
||||
If the car of BACKENDS is nil, unset the backends for MODES.
|
||||
|
||||
Examples:
|
||||
|
||||
(set-company-backend! 'js2-mode
|
||||
'company-tide 'company-yasnippet)
|
||||
|
||||
(set-company-backend! 'sh-mode
|
||||
'(company-shell :with company-yasnippet))
|
||||
|
||||
(set-company-backend! '(c-mode c++-mode)
|
||||
'(:separate company-irony-c-headers company-irony))
|
||||
|
||||
(set-company-backend! 'sh-mode nil) ; unsets backends for sh-mode"
|
||||
(declare (indent defun))
|
||||
(dolist (mode (doom-enlist modes))
|
||||
(if (null (car backends))
|
||||
(setq +company-backend-alist
|
||||
(delq (assq mode +company-backend-alist)
|
||||
+company-backend-alist))
|
||||
(setf (alist-get mode +company-backend-alist)
|
||||
backends))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Library
|
||||
|
||||
(defun +company--backends ()
|
||||
(let (backends)
|
||||
(let ((mode major-mode)
|
||||
(modes (list major-mode)))
|
||||
(while (setq mode (get mode 'derived-mode-parent))
|
||||
(push mode modes))
|
||||
(dolist (mode modes)
|
||||
(dolist (backend (append (cdr (assq mode +company-backend-alist))
|
||||
(default-value 'company-backends)))
|
||||
(push backend backends)))
|
||||
(delete-dups
|
||||
(append (cl-loop for (mode . backends) in +company-backend-alist
|
||||
if (or (eq major-mode mode) ; major modes
|
||||
(and (boundp mode)
|
||||
(symbol-value mode))) ; minor modes
|
||||
append backends)
|
||||
(nreverse backends))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Hooks
|
||||
|
||||
;;;###autoload
|
||||
(defun +company-init-backends-h ()
|
||||
"Set `company-backends' for the current buffer."
|
||||
(or (memq major-mode '(fundamental-mode special-mode))
|
||||
buffer-read-only
|
||||
(doom-temp-buffer-p (or (buffer-base-buffer) (current-buffer)))
|
||||
(setq-local company-backends (+company--backends))))
|
||||
|
||||
(put '+company-init-backends-h 'permanent-local-hook t)
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
;;;###autoload
|
||||
(defun +company-has-completion-p ()
|
||||
"Return non-nil if a completion candidate exists at point."
|
||||
(and (company-manual-begin)
|
||||
(= company-candidates-length 1)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +company/toggle-auto-completion ()
|
||||
"Toggle as-you-type code completion."
|
||||
(interactive)
|
||||
(require 'company)
|
||||
(setq company-idle-delay (unless company-idle-delay 0.2))
|
||||
(message "Auto completion %s"
|
||||
(if company-idle-delay "enabled" "disabled")))
|
||||
|
||||
;;;###autoload
|
||||
(defun +company/complete ()
|
||||
"Bring up the completion popup. If only one result, complete it."
|
||||
(interactive)
|
||||
(require 'company)
|
||||
(when (ignore-errors
|
||||
(/= (point)
|
||||
(cdr (bounds-of-thing-at-point 'symbol))))
|
||||
(save-excursion (insert " ")))
|
||||
(when (and (company-manual-begin)
|
||||
(= company-candidates-length 1))
|
||||
(company-complete-common)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +company/dabbrev ()
|
||||
"Invokes `company-dabbrev-code' in prog-mode buffers and `company-dabbrev'
|
||||
everywhere else."
|
||||
(interactive)
|
||||
(call-interactively
|
||||
(if (derived-mode-p 'prog-mode)
|
||||
#'company-dabbrev-code
|
||||
#'company-dabbrev)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +company/whole-lines (command &optional arg &rest ignored)
|
||||
"`company-mode' completion backend that completes whole-lines, akin to vim's
|
||||
C-x C-l."
|
||||
(interactive (list 'interactive))
|
||||
(require 'company)
|
||||
(pcase command
|
||||
(`interactive (company-begin-backend '+company/whole-lines))
|
||||
(`prefix (company-grab-line "^[\t\s]*\\(.+\\)" 1))
|
||||
(`candidates
|
||||
(all-completions
|
||||
arg
|
||||
(delete-dups
|
||||
(split-string
|
||||
(replace-regexp-in-string
|
||||
"^[\t\s]+" ""
|
||||
(concat (buffer-substring-no-properties (point-min) (line-beginning-position))
|
||||
(buffer-substring-no-properties (line-end-position) (point-max))))
|
||||
"\\(\r\n\\|[\n\r]\\)" t))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +company/dict-or-keywords ()
|
||||
"`company-mode' completion combining `company-dict' and `company-keywords'."
|
||||
(interactive)
|
||||
(require 'company-dict)
|
||||
(require 'company-keywords)
|
||||
(let ((company-backends '((company-keywords company-dict))))
|
||||
(call-interactively #'company-complete)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +company/dabbrev-code-previous ()
|
||||
"TODO"
|
||||
(interactive)
|
||||
(require 'company-dabbrev)
|
||||
(let ((company-selection-wrap-around t))
|
||||
(call-interactively #'+company/dabbrev)
|
||||
(company-select-previous-or-abort)))
|
|
@ -0,0 +1,176 @@
|
|||
;;; completion/company/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package! company
|
||||
:commands company-complete-common company-manual-begin company-grab-line
|
||||
:hook (doom-first-input . global-company-mode)
|
||||
:init
|
||||
(setq company-minimum-prefix-length 2
|
||||
company-tooltip-limit 14
|
||||
company-tooltip-align-annotations t
|
||||
company-require-match 'never
|
||||
company-global-modes '(not erc-mode message-mode help-mode gud-mode)
|
||||
company-frontends
|
||||
'(company-pseudo-tooltip-frontend ; always show candidates in overlay tooltip
|
||||
company-echo-metadata-frontend) ; show selected candidate docs in echo area
|
||||
|
||||
;; Buffer-local backends will be computed when loading a major mode, so
|
||||
;; only specify a global default here.
|
||||
company-backends '(company-capf)
|
||||
|
||||
;; These auto-complete the current selection when
|
||||
;; `company-auto-complete-chars' is typed. This is too magical. We
|
||||
;; already have the much more explicit RET and TAB.
|
||||
company-auto-complete nil
|
||||
company-auto-complete-chars nil
|
||||
|
||||
;; Only search the current buffer for `company-dabbrev' (a backend that
|
||||
;; suggests text your open buffers). This prevents Company from causing
|
||||
;; lag once you have a lot of buffers open.
|
||||
company-dabbrev-other-buffers nil
|
||||
;; Make `company-dabbrev' fully case-sensitive, to improve UX with
|
||||
;; domain-specific words with particular casing.
|
||||
company-dabbrev-ignore-case nil
|
||||
company-dabbrev-downcase nil)
|
||||
|
||||
:config
|
||||
(when (featurep! :editor evil)
|
||||
(add-hook 'company-mode-hook #'evil-normalize-keymaps)
|
||||
(unless (featurep! +childframe)
|
||||
;; Don't persist company popups when switching back to normal mode.
|
||||
;; `company-box' aborts on mode switch so it doesn't need this.
|
||||
(add-hook! 'evil-normal-state-entry-hook
|
||||
(defun +company-abort-h ()
|
||||
;; HACK `company-abort' doesn't no-op if company isn't active; causing
|
||||
;; unwanted side-effects, like the suppression of messages in the
|
||||
;; echo-area.
|
||||
;; REVIEW Revisit this to refactor; shouldn't be necessary!
|
||||
(when company-candidates
|
||||
(company-abort)))))
|
||||
;; Allow users to switch between backends on the fly. E.g. C-x C-s followed
|
||||
;; by C-x C-n, will switch from `company-yasnippet' to
|
||||
;; `company-dabbrev-code'.
|
||||
(defadvice! +company--abort-previous-a (&rest _)
|
||||
:before #'company-begin-backend
|
||||
(company-abort)))
|
||||
|
||||
(add-hook 'after-change-major-mode-hook #'+company-init-backends-h 'append)
|
||||
|
||||
(when (featurep! +tng)
|
||||
(company-tng-mode +1))
|
||||
|
||||
;; NOTE Fix #1335: ensure `company-emulation-alist' is the first item of
|
||||
;; `emulation-mode-map-alists', thus higher priority than keymaps of
|
||||
;; evil-mode. We raise the priority of company-mode keymaps
|
||||
;; unconditionally even when completion is not activated. This should not
|
||||
;; cause problems, because when completion is activated, the value of
|
||||
;; `company-emulation-alist' is ((t . company-my-keymap)), when
|
||||
;; completion is not activated, the value is ((t . nil)).
|
||||
(add-hook! 'evil-local-mode-hook
|
||||
(when (memq 'company-emulation-alist emulation-mode-map-alists)
|
||||
(company-ensure-emulation-alist)))
|
||||
|
||||
;; Fix #4355: allow eldoc to trigger after completions.
|
||||
(after! eldoc
|
||||
(eldoc-add-command 'company-complete-selection
|
||||
'company-complete-common
|
||||
'company-abort)))
|
||||
|
||||
|
||||
;;
|
||||
;;; Packages
|
||||
|
||||
(after! company-files
|
||||
(add-to-list 'company-files--regexps "file:\\(\\(?:\\.\\{1,2\\}/\\|~/\\|/\\)[^\]\n]*\\)"))
|
||||
|
||||
|
||||
(use-package! company-box
|
||||
:when (featurep! +childframe)
|
||||
:hook (company-mode . company-box-mode)
|
||||
:config
|
||||
(setq company-box-show-single-candidate t
|
||||
company-box-backends-colors nil
|
||||
company-box-max-candidates 50
|
||||
company-box-icons-alist 'company-box-icons-all-the-icons
|
||||
company-box-icons-functions
|
||||
(cons #'+company-box-icons--elisp-fn
|
||||
(delq 'company-box-icons--elisp
|
||||
company-box-icons-functions))
|
||||
company-box-icons-all-the-icons
|
||||
(let ((all-the-icons-scale-factor 0.8))
|
||||
`((Unknown . ,(all-the-icons-material "find_in_page" :face 'all-the-icons-purple))
|
||||
(Text . ,(all-the-icons-material "text_fields" :face 'all-the-icons-green))
|
||||
(Method . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
|
||||
(Function . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
|
||||
(Constructor . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
|
||||
(Field . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
|
||||
(Variable . ,(all-the-icons-material "adjust" :face 'all-the-icons-blue))
|
||||
(Class . ,(all-the-icons-material "class" :face 'all-the-icons-red))
|
||||
(Interface . ,(all-the-icons-material "settings_input_component" :face 'all-the-icons-red))
|
||||
(Module . ,(all-the-icons-material "view_module" :face 'all-the-icons-red))
|
||||
(Property . ,(all-the-icons-material "settings" :face 'all-the-icons-red))
|
||||
(Unit . ,(all-the-icons-material "straighten" :face 'all-the-icons-red))
|
||||
(Value . ,(all-the-icons-material "filter_1" :face 'all-the-icons-red))
|
||||
(Enum . ,(all-the-icons-material "plus_one" :face 'all-the-icons-red))
|
||||
(Keyword . ,(all-the-icons-material "filter_center_focus" :face 'all-the-icons-red))
|
||||
(Snippet . ,(all-the-icons-material "short_text" :face 'all-the-icons-red))
|
||||
(Color . ,(all-the-icons-material "color_lens" :face 'all-the-icons-red))
|
||||
(File . ,(all-the-icons-material "insert_drive_file" :face 'all-the-icons-red))
|
||||
(Reference . ,(all-the-icons-material "collections_bookmark" :face 'all-the-icons-red))
|
||||
(Folder . ,(all-the-icons-material "folder" :face 'all-the-icons-red))
|
||||
(EnumMember . ,(all-the-icons-material "people" :face 'all-the-icons-red))
|
||||
(Constant . ,(all-the-icons-material "pause_circle_filled" :face 'all-the-icons-red))
|
||||
(Struct . ,(all-the-icons-material "streetview" :face 'all-the-icons-red))
|
||||
(Event . ,(all-the-icons-material "event" :face 'all-the-icons-red))
|
||||
(Operator . ,(all-the-icons-material "control_point" :face 'all-the-icons-red))
|
||||
(TypeParameter . ,(all-the-icons-material "class" :face 'all-the-icons-red))
|
||||
(Template . ,(all-the-icons-material "short_text" :face 'all-the-icons-green))
|
||||
(ElispFunction . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
|
||||
(ElispVariable . ,(all-the-icons-material "check_circle" :face 'all-the-icons-blue))
|
||||
(ElispFeature . ,(all-the-icons-material "stars" :face 'all-the-icons-orange))
|
||||
(ElispFace . ,(all-the-icons-material "format_paint" :face 'all-the-icons-pink)))))
|
||||
|
||||
;; HACK Fix oversized scrollbar in some odd cases
|
||||
;; REVIEW `resize-mode' is deprecated and may stop working in the future.
|
||||
;; TODO PR me upstream?
|
||||
(setq x-gtk-resize-child-frames 'resize-mode)
|
||||
|
||||
;; Disable tab-bar in company-box child frames
|
||||
;; TODO PR me upstream!
|
||||
(add-to-list 'company-box-frame-parameters '(tab-bar-lines . 0))
|
||||
|
||||
;; Don't show documentation in echo area, because company-box displays its own
|
||||
;; in a child frame.
|
||||
(delq! 'company-echo-metadata-frontend company-frontends)
|
||||
|
||||
(defun +company-box-icons--elisp-fn (candidate)
|
||||
(when (derived-mode-p 'emacs-lisp-mode)
|
||||
(let ((sym (intern candidate)))
|
||||
(cond ((fboundp sym) 'ElispFunction)
|
||||
((boundp sym) 'ElispVariable)
|
||||
((featurep sym) 'ElispFeature)
|
||||
((facep sym) 'ElispFace)))))
|
||||
|
||||
;; `company-box' performs insufficient frame-live-p checks. Any command that
|
||||
;; "cleans up the session" will break company-box.
|
||||
;; TODO Fix this upstream.
|
||||
(defadvice! +company-box-detect-deleted-frame-a (frame)
|
||||
:filter-return #'company-box--get-frame
|
||||
(if (frame-live-p frame) frame))
|
||||
(defadvice! +company-box-detect-deleted-doc-frame-a (_selection frame)
|
||||
:before #'company-box-doc
|
||||
(and company-box-doc-enable
|
||||
(frame-local-getq company-box-doc-frame frame)
|
||||
(not (frame-live-p (frame-local-getq company-box-doc-frame frame)))
|
||||
(frame-local-setq company-box-doc-frame nil frame))))
|
||||
|
||||
|
||||
(use-package! company-dict
|
||||
:defer t
|
||||
:config
|
||||
(setq company-dict-dir (expand-file-name "dicts" doom-private-dir))
|
||||
(add-hook! 'doom-project-hook
|
||||
(defun +company-enable-project-dicts-h (mode &rest _)
|
||||
"Enable per-project dictionaries."
|
||||
(if (symbol-value mode)
|
||||
(add-to-list 'company-dict-minor-mode-list mode nil #'eq)
|
||||
(setq company-dict-minor-mode-list (delq mode company-dict-minor-mode-list))))))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue