branch: elpa/clojure-mode
commit 75fa6336dbfba5cd29a479e59a7dc7ce7f7f4fc2
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
[Fix #687] Improve project root detection with preferred build tool and VCS
tiebreaker
When multiple build tool files exist in the directory hierarchy (e.g. a
source file named project.clj inside a deps.edn project), the most nested
match was always chosen, which could be incorrect.
Add `clojure-preferred-build-tool` defcustom for explicit user preference.
When unset, use `.git` presence as a tiebreaker before falling back to
most nested.
---
CHANGELOG.md | 1 +
clojure-mode.el | 28 ++++++++++++++++++++++++++--
test/clojure-mode-util-test.el | 37 ++++++++++++++++++++++++++++++++++++-
3 files changed, 63 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee2a11e67d..24cb6fcf71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
### New features
+* [#687](https://github.com/clojure-emacs/clojure-mode/issues/687): Add
`clojure-preferred-build-tool` to control project root detection when multiple
build tool files exist. When unset, prefer directories containing `.git` as a
tiebreaker.
* [#688](https://github.com/clojure-emacs/clojure-mode/issues/688): Add
`clojure-discard-face` for `#_` reader discard forms, allowing them to be
styled differently from comments. Inherits from `font-lock-comment-face` by
default.
* Add project root detection for ClojureCLR (`deps-clr.edn`).
diff --git a/clojure-mode.el b/clojure-mode.el
index 093f590d94..903cf92ad1 100644
--- a/clojure-mode.el
+++ b/clojure-mode.el
@@ -267,6 +267,22 @@ The prefixes are used to generate the correct namespace."
:risky t
:package-version '(clojure-mode . "5.7.0"))
+(defcustom clojure-preferred-build-tool nil
+ "Preferred build tool file to identify the project root.
+When multiple build tool files are found in the directory hierarchy,
+this setting controls which one takes precedence.
+
+When nil (the default), prefer directories that also contain a
+version-control marker (`.git'). If that doesn't break the tie,
+fall back to the most nested match.
+
+When set to a string (e.g., \"deps.edn\"), prefer the directory
+containing that specific file."
+ :type '(choice (const :tag "Auto-detect" nil)
+ (string :tag "Build tool filename"))
+ :safe (lambda (value) (or (null value) (stringp value)))
+ :package-version '(clojure-mode . "5.22.0"))
+
(defcustom clojure-refactor-map-prefix (kbd "C-c C-r")
"Clojure refactor keymap prefix."
:type 'string
@@ -2076,8 +2092,16 @@ Return nil if not inside a project."
(mapcar (lambda (fname)
(locate-dominating-file dir-name fname))
clojure-build-tool-files))))
- (when (> (length choices) 0)
- (car (sort choices #'file-in-directory-p)))))
+ (when choices
+ (if clojure-preferred-build-tool
+ ;; When a preferred build tool is set, look for it specifically.
+ (or (locate-dominating-file dir-name clojure-preferred-build-tool)
+ (car (sort choices #'file-in-directory-p)))
+ ;; Otherwise, prefer candidates that contain a .git directory.
+ (or (car (seq-filter (lambda (dir)
+ (file-directory-p (expand-file-name ".git"
dir)))
+ (sort choices #'file-in-directory-p)))
+ (car choices))))))
(defun clojure-project-relative-path (path)
"Denormalize PATH by making it relative to the project root."
diff --git a/test/clojure-mode-util-test.el b/test/clojure-mode-util-test.el
index 2b30cf3665..b0f7099d1c 100644
--- a/test/clojure-mode-util-test.el
+++ b/test/clojure-mode-util-test.el
@@ -47,7 +47,42 @@
(write-region "{}" nil bb-edn)
(make-directory bb-edn-src)
(expect (expand-file-name (clojure-project-dir bb-edn-src))
- :to-equal (file-name-as-directory temp-dir))))))
+ :to-equal (file-name-as-directory temp-dir)))))
+
+ (it "preferred build tool selects matching directory"
+ (with-temp-dir temp-dir
+ (let* ((root (file-name-as-directory temp-dir))
+ (subdir (expand-file-name "src/project.clj" temp-dir))
+ (clojure-preferred-build-tool "deps.edn"))
+ (write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
+ (make-directory (expand-file-name "src" temp-dir))
+ (write-region "" nil subdir)
+ (expect (expand-file-name
+ (clojure-project-root-path (expand-file-name "src/"
temp-dir)))
+ :to-equal root))))
+
+ (it "VCS tiebreaker prefers directory with .git"
+ (with-temp-dir temp-dir
+ (let* ((root (file-name-as-directory temp-dir))
+ (clojure-preferred-build-tool nil))
+ (write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
+ (make-directory (expand-file-name ".git" temp-dir))
+ (make-directory (expand-file-name "src" temp-dir))
+ (write-region "" nil (expand-file-name "src/project.clj" temp-dir))
+ (expect (expand-file-name
+ (clojure-project-root-path (expand-file-name "src/"
temp-dir)))
+ :to-equal root))))
+
+ (it "no preference and no VCS falls back to most nested"
+ (with-temp-dir temp-dir
+ (let* ((subdir (file-name-as-directory (expand-file-name "src"
temp-dir)))
+ (clojure-preferred-build-tool nil))
+ (write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
+ (make-directory (expand-file-name "src" temp-dir))
+ (write-region "" nil (expand-file-name "src/project.clj" temp-dir))
+ (expect (expand-file-name
+ (clojure-project-root-path (expand-file-name "src/"
temp-dir)))
+ :to-equal subdir)))))
(describe "clojure-project-relative-path"
(cl-letf (((symbol-function 'clojure-project-dir) (lambda () project-dir)))