From 073d72120acba3de05184f747630bf7a3641a049 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 14 Feb 2026 15:29:12 +0200 Subject: Include intermediate directories in projectile-find-dir (#1596) projectile-project-dirs previously only returned directories that directly contain files, missing intermediate directories that contain only subdirectories (e.g. src/ when it only has src/ComponentA/). Fix by walking each file path's ancestor directories up to the project root, so all intermediate directories are included. --- CHANGELOG.md | 1 + projectile.el | 15 +++++++++++++-- test/projectile-test.el | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e196f2..7a3a850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * [#1748](https://github.com/bbatsov/projectile/issues/1748): Fix `projectile-replace` falling back to the legacy Emacs 25/26 code path on Emacs 27+ because `fileloop` was not loaded. * [#1741](https://github.com/bbatsov/projectile/issues/1741): Fix `projectile-replace` treating the search string as a regexp instead of a literal string on Emacs 27+. +* [#1596](https://github.com/bbatsov/projectile/issues/1596): `projectile-find-dir` now includes intermediate directories that contain only subdirectories (e.g. `src/` when it only has `src/ComponentA/`, `src/ComponentB/`). * [#1551](https://github.com/bbatsov/projectile/issues/1551): Don't add nonexistent files to the project cache (e.g. when visiting a new file with `find-file` and then abandoning the buffer). * [#1554](https://github.com/bbatsov/projectile/issues/1554): Fix `projectile-files-with-string` failing on special characters when using `grep` or `git-grep` by adding the `-F` (fixed-string) flag. * [#1897](https://github.com/bbatsov/projectile/issues/1897): Filter deleted-but-unstaged files from `git ls-files` output in alien/hybrid indexing (when `fd` is not used). diff --git a/projectile.el b/projectile.el index 6a342da..407fade 100644 --- a/projectile.el +++ b/projectile.el @@ -2281,8 +2281,19 @@ project-root for every file." "Return a list of dirs for PROJECT." (delete-dups (delq nil - (mapcar #'file-name-directory - (projectile-project-files project))))) + (cl-mapcan #'projectile--directory-ancestors + (projectile-project-files project))))) + +(defun projectile--directory-ancestors (path) + "Return a list of the directory of PATH and all its ancestor directories. +For example, \"src/foo/bar.el\" returns (\"src/foo/\" \"src/\")." + (let ((dir (file-name-directory path)) + result) + (while (and dir (not (equal dir ""))) + (push dir result) + (let ((parent (file-name-directory (directory-file-name dir)))) + (setq dir (unless (equal parent dir) parent)))) + result)) (defun projectile-current-project-dirs () "Return a list of dirs for the current project." diff --git a/test/projectile-test.el b/test/projectile-test.el index 7864c09..2d828bd 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -554,6 +554,20 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (expect files :to-contain "existing.txt") (expect files :not :to-contain "deleted.txt"))))))) +(describe "projectile-project-dirs" + (it "includes intermediate directories that contain only subdirectories" + (spy-on 'projectile-project-files + :and-return-value '("src/ComponentA/a.cc" + "src/ComponentB/b.cc" + "config/config_file")) + (let ((dirs (projectile-project-dirs "/project/"))) + ;; Leaf directories + (expect dirs :to-contain "src/ComponentA/") + (expect dirs :to-contain "src/ComponentB/") + (expect dirs :to-contain "config/") + ;; Intermediate directory (only has subdirectories, no direct files) + (expect dirs :to-contain "src/")))) + (describe "projectile-index-directory" (it "skips unreadable directories" (unless (eq system-type 'windows-nt) -- cgit v1.0