2017-06-08 17:47:56 +08:00
|
|
|
;;; core-projects.el -*- lexical-binding: t; -*-
|
2017-02-08 15:02:51 +08:00
|
|
|
|
2019-03-29 06:27:45 +08:00
|
|
|
(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'.")
|
|
|
|
|
2019-04-01 14:48:12 +08:00
|
|
|
(defvar doom-projectile-fd-binary "fd"
|
|
|
|
"name of `fd-find' executable binary")
|
2019-03-29 06:27:45 +08:00
|
|
|
|
|
|
|
;;
|
|
|
|
;;; Packages
|
|
|
|
|
2017-06-08 17:47:56 +08:00
|
|
|
(def-package! projectile
|
2019-03-17 02:20:07 +08:00
|
|
|
:after-call (after-find-file dired-before-readin-hook minibuffer-setup-hook)
|
2019-04-21 03:29:34 +08:00
|
|
|
:commands (projectile-project-root
|
|
|
|
projectile-project-name
|
|
|
|
projectile-project-p
|
|
|
|
projectile-add-known-project) ; TODO PR autoload upstream
|
2017-10-08 19:09:57 +08:00
|
|
|
:init
|
2017-02-20 07:09:26 +08:00
|
|
|
(setq projectile-cache-file (concat doom-cache-dir "projectile.cache")
|
2017-02-01 08:50:11 +08:00
|
|
|
projectile-enable-caching (not noninteractive)
|
2017-02-20 07:09:26 +08:00
|
|
|
projectile-known-projects-file (concat doom-cache-dir "projectile.projects")
|
2018-09-29 02:09:48 +08:00
|
|
|
projectile-require-project-root t
|
2017-09-20 07:39:15 +08:00
|
|
|
projectile-globally-ignored-files '(".DS_Store" "Icon
" "TAGS")
|
2018-05-08 00:14:04 +08:00
|
|
|
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o")
|
2018-12-02 15:02:56 +08:00
|
|
|
projectile-ignored-projects '("~/" "/tmp")
|
2019-03-29 06:27:45 +08:00
|
|
|
projectile-kill-buffers-filter 'kill-only-files
|
2019-05-13 10:12:08 +08:00
|
|
|
projectile-files-cache-expire 604800 ; expire after a week
|
|
|
|
projectile-sort-order 'recentf
|
|
|
|
projectile-use-git-grep t) ; use git-grep for text searches
|
2017-09-20 07:39:15 +08:00
|
|
|
|
2017-10-08 19:09:57 +08:00
|
|
|
:config
|
2018-01-01 00:09:36 +08:00
|
|
|
(add-hook 'dired-before-readin-hook #'projectile-track-known-projects-find-file-hook)
|
2019-03-17 02:20:07 +08:00
|
|
|
(projectile-mode +1)
|
2018-01-01 00:09:36 +08:00
|
|
|
|
2019-03-10 20:12:04 +08:00
|
|
|
(global-set-key [remap evil-jump-to-tag] #'projectile-find-tag)
|
|
|
|
(global-set-key [remap find-tag] #'projectile-find-tag)
|
2019-03-09 15:42:03 +08:00
|
|
|
|
2017-09-20 07:39:15 +08:00
|
|
|
;; a more generic project root file
|
2017-09-19 21:08:24 +08:00
|
|
|
(push ".project" projectile-project-root-files-bottom-up)
|
2019-04-09 11:01:30 +08:00
|
|
|
(push (abbreviate-file-name doom-local-dir) projectile-globally-ignored-directories)
|
2017-09-20 07:39:15 +08:00
|
|
|
|
2019-03-29 06:27:45 +08:00
|
|
|
;; 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'),
|
2019-04-09 11:02:50 +08:00
|
|
|
b) represent blacklisted directories that are too big, change too often or are
|
2019-03-29 06:27:45 +08:00
|
|
|
private. (see `doom-projectile-cache-blacklist'),
|
|
|
|
c) are not valid projectile projects."
|
2019-04-09 11:02:50 +08:00
|
|
|
(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)
|
2019-05-07 05:12:28 +08:00
|
|
|
if (or (not (stringp proot))
|
|
|
|
(>= (length (gethash proot projectile-projects-cache))
|
|
|
|
doom-projectile-cache-limit)
|
2019-04-09 11:02:50 +08:00
|
|
|
(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)))
|
2019-04-25 06:04:17 +08:00
|
|
|
(unless noninteractive
|
|
|
|
(add-hook 'kill-emacs-hook #'doom|cleanup-project-cache))
|
2019-03-29 06:27:45 +08:00
|
|
|
|
2018-08-26 18:19:47 +08:00
|
|
|
;; 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
|
2018-09-28 11:04:16 +08:00
|
|
|
;; prevent issues. This makes project resolution a little slower and less
|
|
|
|
;; accurate in some cases.
|
2018-08-26 18:19:47 +08:00
|
|
|
(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)))
|
|
|
|
|
2017-07-27 00:42:33 +08:00
|
|
|
;; Projectile root-searching functions can cause an infinite loop on TRAMP
|
|
|
|
;; connections, so disable them.
|
2018-09-29 01:54:20 +08:00
|
|
|
;; TODO Is this still necessary?
|
2018-09-08 09:43:32 +08:00
|
|
|
(defun doom*projectile-locate-dominating-file (orig-fn file name)
|
2017-09-20 07:39:15 +08:00
|
|
|
"Don't traverse the file system if on a remote connection."
|
2019-03-29 04:43:04 +08:00
|
|
|
(when (and (stringp file)
|
|
|
|
(not (file-remote-p file)))
|
2018-09-08 09:43:32 +08:00
|
|
|
(funcall orig-fn file name)))
|
2018-12-04 17:09:44 +08:00
|
|
|
(advice-add #'projectile-locate-dominating-file :around #'doom*projectile-locate-dominating-file)
|
|
|
|
|
2019-05-13 10:12:04 +08:00
|
|
|
(cond
|
2019-05-14 02:37:00 +08:00
|
|
|
;; If fd exists, use it for git and generic projects. fd is a rust program
|
2019-06-18 20:18:01 +08:00
|
|
|
;; that is significantly faster than git ls-files or find, and it respects
|
|
|
|
;; .gitignore. This is recommended in the projectile docs.
|
2019-05-13 10:12:04 +08:00
|
|
|
((executable-find doom-projectile-fd-binary)
|
2019-04-01 14:48:12 +08:00
|
|
|
(setq projectile-git-command (concat
|
|
|
|
doom-projectile-fd-binary
|
2019-06-01 20:19:30 +08:00
|
|
|
" . --color=never --type f -0 -H -E .git")
|
2019-06-18 20:43:15 +08:00
|
|
|
projectile-generic-command projectile-git-command
|
|
|
|
;; ensure Windows users get fd's benefits
|
|
|
|
projectile-indexing-method 'alien))
|
2019-05-13 10:12:04 +08:00
|
|
|
|
2019-06-18 20:18:01 +08:00
|
|
|
;; Otherwise, resort to ripgrep, which is also faster than find
|
2019-05-13 10:12:04 +08:00
|
|
|
((executable-find "rg")
|
|
|
|
(setq projectile-generic-command
|
|
|
|
(concat "rg -0 --files --color=never --hidden"
|
|
|
|
(cl-loop for dir in projectile-globally-ignored-directories
|
2019-06-18 20:43:15 +08:00
|
|
|
concat (format " --glob '!%s'" dir)))
|
|
|
|
;; ensure Windows users get fd's benefits
|
|
|
|
projectile-indexing-method 'alien))))
|
2017-02-01 08:50:11 +08:00
|
|
|
|
2017-03-03 07:16:52 +08:00
|
|
|
;;
|
2018-09-08 07:36:16 +08:00
|
|
|
;; Project-based minor modes
|
2017-03-03 07:16:52 +08:00
|
|
|
|
2018-01-01 00:21:53 +08:00
|
|
|
(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.")
|
2017-03-03 07:16:52 +08:00
|
|
|
|
2018-06-16 17:34:19 +08:00
|
|
|
(cl-defmacro def-project-mode! (name &key
|
|
|
|
modes
|
|
|
|
files
|
|
|
|
when
|
|
|
|
match
|
|
|
|
add-hooks
|
|
|
|
on-load
|
|
|
|
on-enter
|
|
|
|
on-exit)
|
2017-09-20 07:39:15 +08:00
|
|
|
"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
|
2019-03-02 14:12:48 +08:00
|
|
|
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.
|
2017-03-03 07:16:52 +08:00
|
|
|
|
|
|
|
This creates NAME-hook and NAME-map as well.
|
|
|
|
|
2018-01-01 00:21:53 +08:00
|
|
|
A project can be enabled through .dir-locals.el too, by setting `doom-project'.
|
2017-03-03 07:16:52 +08:00
|
|
|
|
2017-06-16 20:34:28 +08:00
|
|
|
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.
|
2017-03-03 07:16:52 +08:00
|
|
|
|
|
|
|
:modes MODES -- if buffers are derived from MODES (one or a list of symbols).
|
|
|
|
|
2017-06-16 20:34:28 +08:00
|
|
|
: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
|
2017-03-16 10:42:05 +08:00
|
|
|
project root, however, if prefixed with a '.' or '..', it is relative to the
|
|
|
|
current buffer.
|
2017-03-03 07:16:52 +08:00
|
|
|
|
|
|
|
:match REGEXP -- if file name matches REGEXP
|
|
|
|
|
|
|
|
:when PREDICATE -- if PREDICATE returns true (can be a form or the symbol of a
|
|
|
|
function)
|
|
|
|
|
2017-10-04 23:42:37 +08:00
|
|
|
: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.
|
2017-06-16 20:34:28 +08:00
|
|
|
|
2017-10-04 23:42:37 +08:00
|
|
|
:on-exit FORM -- FORM is run each time the mode is disabled.
|
2017-10-03 09:02:53 +08:00
|
|
|
|
2017-06-16 20:34:28 +08:00
|
|
|
Relevant: `doom-project-hook'."
|
2018-06-16 17:34:19 +08:00
|
|
|
(declare (indent 1))
|
|
|
|
(let ((init-var (intern (format "%s-init" name))))
|
2017-03-03 07:16:52 +08:00
|
|
|
`(progn
|
2018-06-16 17:34:19 +08:00
|
|
|
,(if on-load `(defvar ,init-var nil))
|
2017-03-03 07:16:52 +08:00
|
|
|
(define-minor-mode ,name
|
2018-06-16 17:34:19 +08:00
|
|
|
"A project minor mode generated by `def-project-mode!'."
|
2017-03-03 07:16:52 +08:00
|
|
|
:init-value nil
|
2017-10-04 23:42:37 +08:00
|
|
|
:lighter ""
|
|
|
|
:keymap (make-sparse-keymap)
|
|
|
|
(if (not ,name)
|
2018-06-16 17:34:19 +08:00
|
|
|
,on-exit
|
2018-01-01 00:21:53 +08:00
|
|
|
(run-hook-with-args 'doom-project-hook ',name ,name)
|
2018-06-16 17:34:19 +08:00
|
|
|
,(when on-load
|
2017-10-04 23:42:37 +08:00
|
|
|
`(unless ,init-var
|
2018-06-16 17:34:19 +08:00
|
|
|
,on-load
|
2017-10-04 23:42:37 +08:00
|
|
|
(setq ,init-var t)))
|
2018-06-16 17:34:19 +08:00
|
|
|
,on-enter))
|
2018-08-11 02:37:12 +08:00
|
|
|
,@(cl-loop for hook in add-hooks
|
|
|
|
collect `(add-hook ',(intern (format "%s-hook" name))
|
|
|
|
#',hook))
|
2017-03-03 07:16:52 +08:00
|
|
|
,(when (or modes match files when)
|
|
|
|
`(associate! ,name
|
|
|
|
:modes ,modes
|
|
|
|
:match ,match
|
|
|
|
:files ,files
|
2017-10-04 23:42:37 +08:00
|
|
|
:when ,when)))))
|
2017-02-01 08:50:11 +08:00
|
|
|
|
|
|
|
(provide 'core-projects)
|
|
|
|
;;; core-projects.el ends here
|