doomemacs/core/core-projects.el
Henrik Lissner 6cb5efc929
core-lib: rename helper fns; move doom-resolve-vim-path
+ doom--resolve-paths => doom--resolve-path-forms
+ doom--resolve-hooks => doom--resolve-hook-forms
+ +evil*ex-replace-special-filenames => doom-resolve-vim-path
2017-09-27 01:23:54 +02:00

150 lines
5.6 KiB
EmacsLisp

;;; core-projects.el -*- lexical-binding: t; -*-
(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.")
(def-package! projectile
:demand t
:init (add-hook 'doom-init-hook #'projectile-mode)
:config
(setq projectile-cache-file (concat doom-cache-dir "projectile.cache")
projectile-enable-caching (not noninteractive)
projectile-indexing-method 'alien
projectile-known-projects-file (concat doom-cache-dir "projectile.projects")
projectile-require-project-root nil
projectile-globally-ignored-files '(".DS_Store" "Icon
" "TAGS")
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o"))
;; a more generic project root file
(push ".project" projectile-project-root-files-bottom-up)
(nconc projectile-globally-ignored-directories (list doom-local-dir ".sync"))
(nconc projectile-other-file-alist '(("css" . ("scss" "sass" "less" "style"))
("scss" . ("css"))
("sass" . ("css"))
("less" . ("css"))
("styl" . ("css"))))
;; Projectile root-searching functions can cause an infinite loop on TRAMP
;; connections, so disable them.
(defun doom*projectile-locate-dominating-file (orig-fn &rest args)
"Don't traverse the file system if on a remote connection."
(unless (file-remote-p default-directory)
(apply orig-fn args)))
(advice-add #'projectile-locate-dominating-file :around #'doom*projectile-locate-dominating-file)
(defun doom*projectile-cache-current-file (orig-fun &rest args)
"Don't cache ignored files."
(unless (cl-loop for path in (projectile-ignored-directories)
if (string-prefix-p buffer-file-name (expand-file-name path))
return t)
(apply orig-fun args)))
(advice-add #'projectile-cache-current-file :around #'doom*projectile-cache-current-file))
;;
;; Library
;;
(defun doom/reload-project ()
"Reload the project root cache."
(interactive)
(projectile-invalidate-cache nil)
(projectile-reset-cached-project-root)
(dolist (fn projectile-project-root-files-functions)
(remhash (format "%s-%s" fn default-directory) projectile-project-root-cache)))
(defun doom-project-p ()
"Whether or not this buffer is currently in a project or not."
(let ((projectile-require-project-root t))
(projectile-project-p)))
(defun doom-project-root ()
"Get the path to the root of your project.
If STRICT-P, return nil if no project was found, otherwise return
`default-directory'."
(let (projectile-require-project-root)
(projectile-project-root)))
(defmacro doom-project-has! (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."
(doom--resolve-path-forms files (doom-project-root)))
;;
;; Projects
;;
(defvar-local doom-project nil
"A list of project mode to enable. Used for .dir-locals.el.")
(defun doom|autoload-project-mode ()
"Auto-enable projects listed in `doom-project', which is meant to be set from
.dir-locals.el files."
(dolist (mode doom-project)
(funcall mode)))
(add-hook 'after-change-major-mode-hook #'doom|autoload-project-mode)
(defmacro def-project-mode! (name &rest plist)
"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 specific folder, certain project structure,
framework or 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, if `doom-project' is set to
the name (symbol) of the project mode(s) to enable.
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)
:init FORM -- FORM to run the first time this project mode is enabled.
Relevant: `doom-project-hook'."
(declare (indent 1))
(let ((modes (plist-get plist :modes))
(files (plist-get plist :files))
(when (plist-get plist :when))
(match (plist-get plist :match))
(init-form (plist-get plist :init))
(keymap-sym (intern (format "%s-map" name))))
`(progn
(defvar ,keymap-sym (make-sparse-keymap)
,(concat "Keymap for `" (symbol-name name) "'"))
(define-minor-mode ,name
"A project minor mode."
:init-value nil
:keymap ,keymap-sym)
,(when (or modes match files when)
`(associate! ,name
:modes ,modes
:match ,match
:files ,files
:when ,when))
(add-hook! ,name
(run-hook-with-args 'doom-project-hook ',name))
,(when init-form
`(add-transient-hook! ',(intern (format "%s-hook" name))
,init-form)))))
(provide 'core-projects)
;;; core-projects.el ends here