doomemacs/core/core-projects.el
Henrik Lissner 9ed322ded6
Remove recentf-filename-handlers fix for projectile-recentf-files
This is unnecessary now that path abbreviation occurs in an ivy
transformer rather than as a handler in recentf-filename-handlers.
2017-08-21 21:58:53 +02:00

152 lines
5.7 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-file-exists-remote-cache-expire nil
projectile-indexing-method 'alien
projectile-known-projects-file (concat doom-cache-dir "projectile.projects")
projectile-require-project-root nil
projectile-project-root-files
'(".git" ".hg" ".svn" ".project" "package.json" "setup.py" "Gemfile"
"build.gradle")
projectile-other-file-alist
(append '(("less" "css")
("styl" "css")
("sass" "css")
("scss" "css")
("css" "scss" "sass" "less" "styl")
("jade" "html")
("pug" "html")
("html" "jade" "pug" "jsx" "tsx"))
projectile-other-file-alist)
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o")
projectile-globally-ignored-files '(".DS_Store" "Icon
")
projectile-globally-ignored-directories
(append (list doom-local-dir ".sync")
projectile-globally-ignored-directories))
;; 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 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-some (lambda (path)
(string-prefix-p buffer-file-name
(expand-file-name path)))
(projectile-ignored-directories))
(apply orig-fun args)))
(advice-add #'projectile-cache-current-file :around #'doom*projectile-cache-current-file))
;;
;; Library
;;
(defun doom-project-p (&optional strict-p)
"Whether or not this buffer is currently in a project or not."
(let ((projectile-require-project-root strict-p))
(projectile-project-p)))
(defun doom-project-root (&optional strict-p)
"Get the path to the root of your project."
(let ((projectile-require-project-root strict-p))
(ignore-errors (projectile-project-root))))
(defun doom*project-root (&rest _)
"An advice function used to replace project-root-detection functions in other
libraries."
(doom-project-root))
(defmacro doom-project-has! (files)
"Checks if the project has the specified FILES, relative to the project root,
unless the path begins with ./ or ../, in which case it's relative to
`default-directory'. Recognizes (and ...) and/or (or ...) forms."
(doom--resolve-paths files (doom-project-root)))
;;
;; Projects
;;
(defvar-local doom-project nil
"A list of project mode symbols 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 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