This is first of three big naming convention updates that have been a long time coming. With 2.1 on the horizon, all the breaking updates will batched together in preparation for the long haul. In this commit, we do away with the asterix to communicate that a function is an advice function, and we replace it with the '-a' suffix. e.g. doom*shut-up -> doom-shut-up-a doom*recenter -> doom-recenter-a +evil*static-reindent -> +evil--static-reindent-a The rationale behind this change is: 1. Elisp's own formatting/indenting tools would occasionally struggle with | and * (particularly pp and cl-prettyprint). They have no problem with / and :, fortunately. 2. External syntax highlighters (like pygmentize, discord markdown or github markdown) struggle with it, sometimes refusing to highlight code beyond these symbols. 3. * and | are less expressive than - and -- in communicating the intended visibility, versatility and stability of a function. 4. It complicated the regexps we must use to search for them. 5. They were arbitrary and over-complicated to begin with, decided on haphazardly way back when Doom was simply "my private config". Anyhow, like how predicate functions have the -p suffix, we'll adopt the -a suffix for advice functions, -h for hook functions and -fn for variable functions. Other noteable changes: - Replaces advice-{add,remove}! macro with new def-advice! macro. The old pair weren't as useful. The new def-advice! saves on a lot of space. - Removed "stage" assertions to make sure you were using the right macros in the right place. Turned out to not be necessary, we'll employ better checks later.
229 lines
9.7 KiB
EmacsLisp
229 lines
9.7 KiB
EmacsLisp
;;; core-projects.el -*- lexical-binding: t; -*-
|
|
|
|
(defvar doom-projectile-cache-limit 25000
|
|
"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 '("fd" "fdfind"))
|
|
"name of `fd-find' executable binary")
|
|
|
|
(defvar doom-projectile-cache-timer-file (concat doom-cache-dir "projectile.timers")
|
|
"Where to save project file cache timers.")
|
|
|
|
|
|
;;
|
|
;;; Packages
|
|
|
|
(def-package! projectile
|
|
:after-call (after-find-file dired-before-readin-hook minibuffer-setup-hook)
|
|
:commands (projectile-project-root
|
|
projectile-project-name
|
|
projectile-project-p
|
|
projectile-add-known-project) ; TODO PR autoload upstream
|
|
:init
|
|
(setq projectile-cache-file (concat doom-cache-dir "projectile.cache")
|
|
projectile-enable-caching (not noninteractive)
|
|
projectile-known-projects-file (concat doom-cache-dir "projectile.projects")
|
|
projectile-require-project-root t
|
|
projectile-globally-ignored-files '(".DS_Store" "Icon
|
|
" "TAGS")
|
|
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o")
|
|
projectile-ignored-projects '("~/" "/tmp")
|
|
projectile-kill-buffers-filter 'kill-only-files
|
|
projectile-files-cache-expire 604800 ; expire after a week
|
|
projectile-sort-order 'recentf
|
|
projectile-use-git-grep t) ; use git-grep for text searches
|
|
|
|
(global-set-key [remap evil-jump-to-tag] #'projectile-find-tag)
|
|
(global-set-key [remap find-tag] #'projectile-find-tag)
|
|
|
|
:config
|
|
(projectile-mode +1)
|
|
|
|
;; a more generic project root file
|
|
(push ".project" projectile-project-root-files-bottom-up)
|
|
(push (abbreviate-file-name doom-local-dir) projectile-globally-ignored-directories)
|
|
|
|
;; 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.
|
|
(defun doom|cleanup-project-cache ()
|
|
"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 (bound-and-true-p projectile-projects-cache)
|
|
(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)))
|
|
(unless noninteractive
|
|
(add-hook 'kill-emacs-hook #'doom|cleanup-project-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)
|
|
(message "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)))
|
|
|
|
(cond
|
|
;; If fd exists, use it for git and generic projects. fd is a rust program
|
|
;; that is significantly faster than git ls-files or find, and it respects
|
|
;; .gitignore. This is recommended in the projectile docs.
|
|
(doom-projectile-fd-binary
|
|
(setq projectile-git-command (concat
|
|
doom-projectile-fd-binary
|
|
" . --color=never --type f -0 -H -E .git")
|
|
projectile-generic-command projectile-git-command
|
|
;; ensure Windows users get fd's benefits
|
|
projectile-indexing-method 'alien))
|
|
|
|
;; Otherwise, resort to ripgrep, which is also faster than find
|
|
((executable-find "rg")
|
|
(setq projectile-generic-command
|
|
(concat "rg -0 --files --color=never --hidden"
|
|
(cl-loop for dir in projectile-globally-ignored-directories
|
|
concat (format " --glob '!%s'" dir)))
|
|
;; ensure Windows users get rg's benefits
|
|
projectile-indexing-method 'alien)
|
|
;; fix breakage on windows in git projects
|
|
(unless (executable-find "tr")
|
|
(setq projectile-git-submodule-command nil))))
|
|
|
|
(def-advice! doom--projectile-cache-timers-a ()
|
|
"Persist `projectile-projects-cache-time' across sessions, so that
|
|
`projectile-files-cache-expire' checks won't reset when restarting Emacs."
|
|
:before #'projectile-serialize-cache
|
|
(projectile-serialize projectile-projects-cache-time doom-projectile-cache-timer-file))
|
|
;; Restore it
|
|
(setq projectile-projects-cache-time (projectile-unserialize doom-projectile-cache-timer-file))
|
|
|
|
(def-advice! 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)))
|
|
|
|
;; Projectile root-searching functions can cause an infinite loop on TRAMP
|
|
;; connections, so disable them.
|
|
;; TODO Is this still necessary?
|
|
(def-advice! doom--projectile-locate-dominating-file-a (orig-fn file name)
|
|
"Don't traverse the file system if on a remote connection."
|
|
:around #'projectile-locate-dominating-file
|
|
(when (and (stringp file)
|
|
(not (file-remote-p file nil t)))
|
|
(funcall orig-fn file name))))
|
|
|
|
|
|
;;
|
|
;; 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 (a symbol) and declare where and how
|
|
it is activated. Project modes allow you to configure 'sub-modes' for
|
|
major-modes that are specific to a folder, project structure, framework or
|
|
whatever arbitrary context you define. These project modes can have their own
|
|
settings, keymaps, hooks, snippets, etc.
|
|
|
|
This creates NAME-hook and NAME-map as well.
|
|
|
|
A project can be enabled through .dir-locals.el too, by setting `doom-project'.
|
|
|
|
PLIST may contain any of these properties, which are all checked to see if NAME
|
|
should be activated. If they are *all* true, NAME is activated.
|
|
|
|
: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))))
|
|
`(progn
|
|
,(if 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))
|
|
,@(cl-loop for hook in add-hooks
|
|
collect `(add-hook ',(intern (format "%s-hook" name))
|
|
#',hook))
|
|
,(when (or modes match files when)
|
|
`(associate! ,name
|
|
:modes ,modes
|
|
:match ,match
|
|
:files ,files
|
|
:when ,when)))))
|
|
|
|
(provide 'core-projects)
|
|
;;; core-projects.el ends here
|