2022-07-30 21:49:00 +02:00
|
|
|
;;; lisp/lib/projects.el -*- lexical-binding: t; -*-
|
2018-06-16 11:42:37 +02:00
|
|
|
|
2021-04-25 18:33:23 -04:00
|
|
|
;; HACK We forward declare these variables because they are let-bound in a
|
|
|
|
;; number of places with no guarantee that they've been defined yet (i.e.
|
|
|
|
;; that `projectile' is loaded). If a variable is defined with `defvar'
|
|
|
|
;; while it is lexically bound, you get "Defining as dynamic an already
|
|
|
|
;; lexical var" errors in Emacs 28+).
|
|
|
|
;;;###autoload (defvar projectile-project-root nil)
|
2022-06-24 21:15:31 +02:00
|
|
|
;;;###autoload (defvar projectile-enable-caching (not noninteractive))
|
2021-04-25 18:33:23 -04:00
|
|
|
;;;###autoload (defvar projectile-require-project-root 'prompt)
|
2019-03-02 01:08:56 -05:00
|
|
|
|
2019-06-27 17:16:40 +02:00
|
|
|
;;;###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
|
2019-07-09 01:04:36 +02:00
|
|
|
'marker-files predicate
|
2019-06-27 17:16:40 +02:00
|
|
|
'compilation-dir dir
|
|
|
|
'configure-command configure
|
|
|
|
'compile-command compile
|
|
|
|
'test-command test
|
|
|
|
'run-command run))))
|
|
|
|
|
2019-03-02 01:08:56 -05:00
|
|
|
|
2018-06-16 11:42:37 +02:00
|
|
|
;;
|
2019-06-27 17:16:40 +02:00
|
|
|
;;; Macros
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2023-08-18 19:04:25 +02:00
|
|
|
(defmacro project-file-exists-p! (files &optional base-directory)
|
|
|
|
"Checks if FILES exist at the current project's root.
|
|
|
|
|
|
|
|
The project's root is determined by `projectile', starting from BASE-DIRECTORY
|
|
|
|
(defaults to `default-directory'). FILES are paths relative to the project root,
|
|
|
|
unless they begin with a slash."
|
2023-08-19 15:57:32 -05:00
|
|
|
`(file-exists-p! ,files (doom-project-root ,base-directory)))
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
;;
|
2019-06-27 17:16:40 +02:00
|
|
|
;;; Commands
|
2018-06-16 11:42:37 +02:00
|
|
|
|
2019-02-26 13:21:16 -05:00
|
|
|
;;;###autoload
|
|
|
|
(defun doom/find-file-in-other-project (project-root)
|
2022-01-09 17:16:57 +01:00
|
|
|
"Performs `projectile-find-file' in a known project of your choosing."
|
2019-02-26 13:21:16 -05:00
|
|
|
(interactive
|
|
|
|
(list
|
2020-05-26 14:02:06 -04:00
|
|
|
(completing-read "Find file in project: " (projectile-relevant-known-projects))))
|
2019-02-26 13:21:16 -05:00
|
|
|
(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)
|
2022-01-09 17:16:57 +01:00
|
|
|
"Performs `find-file' in a known project of your choosing."
|
2019-02-26 13:21:16 -05:00
|
|
|
(interactive
|
|
|
|
(list
|
2020-05-26 14:02:06 -04:00
|
|
|
(completing-read "Browse in project: " (projectile-relevant-known-projects))))
|
2019-02-26 13:21:16 -05:00
|
|
|
(unless (file-directory-p project-root)
|
|
|
|
(error "Project directory '%s' doesn't exist" project-root))
|
|
|
|
(doom-project-browse project-root))
|
|
|
|
|
2020-11-18 19:56:47 -05:00
|
|
|
;;;###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))
|
|
|
|
|
2021-09-26 13:45:25 +02:00
|
|
|
;;;###autoload
|
|
|
|
(defun doom/add-directory-as-project (dir)
|
|
|
|
"Register an arbitrary directory as a project.
|
2022-06-10 13:11:31 +02:00
|
|
|
|
|
|
|
Unlike `projectile-add-known-project', if DIR isn't a valid project, a .project
|
|
|
|
file will be created within it so that it will always be treated as one. This
|
2021-09-26 13:45:25 +02:00
|
|
|
command will throw an error if a parent of DIR is a valid project (which would
|
|
|
|
mask DIR)."
|
|
|
|
(interactive "D")
|
|
|
|
(let ((short-dir (abbreviate-file-name dir)))
|
|
|
|
(unless (file-equal-p (doom-project-root dir) dir)
|
|
|
|
(with-temp-file (doom-path dir ".project")))
|
|
|
|
(let ((proj-dir (doom-project-root dir)))
|
|
|
|
(unless (file-equal-p proj-dir dir)
|
|
|
|
(user-error "Can't add %S as a project, because %S is already a project"
|
|
|
|
short-dir (abbreviate-file-name proj-dir)))
|
|
|
|
(message "%S was not a project; adding .project file to it"
|
|
|
|
short-dir (abbreviate-file-name proj-dir))
|
2022-06-10 13:11:31 +02:00
|
|
|
(projectile-add-known-project dir))))
|
2021-09-26 13:45:25 +02:00
|
|
|
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
;;
|
2019-06-27 17:16:40 +02:00
|
|
|
;;; Library
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2019-04-09 03:13:18 -04:00
|
|
|
(defun doom-project-p (&optional dir)
|
|
|
|
"Return t if DIR (defaults to `default-directory') is a valid project."
|
|
|
|
(and (doom-project-root dir)
|
|
|
|
t))
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2019-04-09 03:13:18 -04:00
|
|
|
(defun doom-project-root (&optional dir)
|
|
|
|
"Return the project root of DIR (defaults to `default-directory').
|
|
|
|
Returns nil if not in a project."
|
2021-03-27 18:08:56 -04:00
|
|
|
(let ((projectile-project-root
|
|
|
|
(unless dir (bound-and-true-p projectile-project-root)))
|
2019-04-09 03:13:18 -04:00
|
|
|
projectile-require-project-root)
|
|
|
|
(projectile-project-root dir)))
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2018-09-28 13:54:20 -04:00
|
|
|
(defun doom-project-name (&optional dir)
|
2019-04-09 03:13:18 -04:00
|
|
|
"Return the name of the current project.
|
|
|
|
|
|
|
|
Returns '-' if not in a valid project."
|
2019-08-06 14:50:42 -04:00
|
|
|
(if-let (project-root (or (doom-project-root dir)
|
|
|
|
(if dir (expand-file-name dir))))
|
2019-04-09 03:13:18 -04:00
|
|
|
(funcall projectile-project-name-function project-root)
|
|
|
|
"-"))
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2018-09-28 13:54:20 -04:00
|
|
|
(defun doom-project-expand (name &optional dir)
|
|
|
|
"Expand NAME to project root."
|
2019-04-09 03:13:18 -04:00
|
|
|
(expand-file-name name (doom-project-root dir)))
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun doom-project-find-file (dir)
|
2019-04-19 17:30:47 -04:00
|
|
|
"Jump to a file in DIR (searched recursively).
|
2019-03-28 15:07:14 -04:00
|
|
|
|
2019-04-19 17:30:47 -04:00
|
|
|
If DIR is not a project, it will be indexed (but not cached)."
|
2019-03-26 03:38:37 -04:00
|
|
|
(unless (file-directory-p dir)
|
|
|
|
(error "Directory %S does not exist" dir))
|
2019-06-17 20:57:29 +02:00
|
|
|
(unless (file-readable-p dir)
|
|
|
|
(error "Directory %S isn't readable" dir))
|
2020-10-05 16:12:33 -04:00
|
|
|
(let* ((default-directory (file-truename dir))
|
|
|
|
(projectile-project-root (doom-project-root dir))
|
2019-04-19 17:30:47 -04:00
|
|
|
(projectile-enable-caching projectile-enable-caching))
|
2020-04-30 19:01:10 -04:00
|
|
|
(cond ((and projectile-project-root (file-equal-p projectile-project-root default-directory))
|
|
|
|
(unless (doom-project-p default-directory)
|
2019-04-19 17:30:47 -04:00
|
|
|
;; 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
|
2019-08-06 19:42:05 -04:00
|
|
|
;; asynchronously, and thus doesn't see the lexical
|
|
|
|
;; `default-directory'
|
2019-07-22 22:28:43 +02:00
|
|
|
(if (doom-module-p :completion 'ivy)
|
2019-04-19 17:30:47 -04:00
|
|
|
#'counsel-projectile-find-file
|
|
|
|
#'projectile-find-file)))
|
2021-07-02 23:32:01 +03:00
|
|
|
((and (bound-and-true-p ivy-mode)
|
|
|
|
(fboundp 'counsel-file-jump))
|
2019-04-19 17:30:47 -04:00
|
|
|
(call-interactively #'counsel-file-jump))
|
2021-07-02 23:32:01 +03:00
|
|
|
((and (bound-and-true-p helm-mode)
|
|
|
|
(fboundp 'helm-find-files))
|
2019-05-13 00:18:51 -04:00
|
|
|
(call-interactively #'helm-find-files))
|
2024-07-16 11:06:39 -04:00
|
|
|
((when-let* ((project-current-directory-override t)
|
|
|
|
(pr (project-current t dir)))
|
|
|
|
(condition-case _
|
|
|
|
(project-find-file-in nil nil pr)
|
|
|
|
;; FIX: project.el throws errors if DIR is an empty directory,
|
|
|
|
;; which is poor UX.
|
|
|
|
(wrong-type-argument
|
|
|
|
(call-interactively #'find-file)))))
|
2019-04-19 17:30:47 -04:00
|
|
|
((call-interactively #'find-file)))))
|
2018-06-16 11:42:37 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun doom-project-browse (dir)
|
|
|
|
"Traverse a file structure starting linearly from DIR."
|
2019-03-28 18:29:50 -04:00
|
|
|
(let ((default-directory (file-truename (expand-file-name dir))))
|
2018-06-16 11:42:37 +02:00
|
|
|
(call-interactively
|
2019-07-22 22:28:43 +02:00
|
|
|
(cond ((doom-module-p :completion 'ivy)
|
2019-03-26 03:38:37 -04:00
|
|
|
#'counsel-find-file)
|
2019-07-22 22:28:43 +02:00
|
|
|
((doom-module-p :completion 'helm)
|
2019-03-26 03:38:37 -04:00
|
|
|
#'helm-find-files)
|
|
|
|
(#'find-file)))))
|
2021-03-06 11:41:47 -05:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun doom-project-ignored-p (project-root)
|
2021-07-11 10:48:07 -04:00
|
|
|
"Return non-nil if temporary file or a straight package."
|
|
|
|
(unless (file-remote-p project-root)
|
|
|
|
(or (file-in-directory-p project-root temporary-file-directory)
|
|
|
|
(file-in-directory-p project-root doom-local-dir))))
|