projectile icon indicating copy to clipboard operation
projectile copied to clipboard

projectile-find-dir returns only terminal directories, not all directories in a project

Open zeus-hammer opened this issue 5 years ago • 4 comments

Expected behavior

I expect projectile-find-dir to return a list of all directories within a project.

Actual behavior

projectile-find-dir returns directories under which there is a file that projectile is tracking directly underneath the directory. Directories which contain only sub-directories are not available in completion.

Steps to reproduce the problem

Consider the directory structure below:

root/
    src/
        ComponentA/
                      a.cc
        ComponentB/
                      b.cc
        ComponentC/
                      c.cc
    config/
            config_file_1
            config_file_2

When using projectile-find-dir within root, the path root/src is not listed. The paths listed are:

root/src/ComponentA
root/src/ComponentB
root/src/ComponentC
root/config

Environment & Version information

Projectile version information

Projectile 20201030.1132

Emacs version

GNU Emacs 26.3 (build 2, x86_64-pc-linux-gnu, GTK+ Version 3.18.9)

Operating system

Ubuntu 16.04 (4.4.0-139-generic)

zeus-hammer avatar Nov 06 '20 18:11 zeus-hammer

The crux of the issue is in projectile.el.

In this function:

;;;; Find directory in project functionality
(defun projectile--find-dir (invalidate-cache &optional dired-variant)
  "Jump to a project's directory using completion.

With INVALIDATE-CACHE invalidates the cache first.  With DIRED-VARIANT set to a
defun, use that instead of `dired'.  A typical example of such a defun would be
`dired-other-window' or `dired-other-frame'"
  (projectile-maybe-invalidate-cache invalidate-cache)
  (let* ((project (projectile-ensure-project (projectile-project-root)))
         (dir (projectile-complete-dir project))
         (dired-v (or dired-variant #'dired)))
    (funcall dired-v (expand-file-name dir project))
    (run-hooks 'projectile-find-dir-hook)))

projectile-complete-dir eventually calls projectile-project-dirs, which does the following:

(defun projectile-project-dirs (project)
  "Return a list of dirs for PROJECT."
  (delete-dups
   (delq nil
         (mapcar #'file-name-directory
                 (projectile-project-files project)))))

As you can see, it's simply listing all the projectile tracked files in the project and slicing off the file name from the path, leaving us with the directory path. This misses out on directories which only have directories as children.

zeus-hammer avatar Nov 06 '20 18:11 zeus-hammer

I'm working on hacking this up on my local copy, but it's inelegant and doesn't take advantage of projectile's caching. I will likely substitute this for #'file-name-directory and flatten the whole list. I can help with this issue as well, I just don't know the best way to go about it as there are multiple routes. My hack would likely be untenable for large projects.

(defun get-dirs-from-path (path)
  (let ((directories '()))
        (while (not (or (equal path "/") (equal path "~")))
          (setq path (file-name-directory (directory-file-name path)))
          (message "path: %s" path)
          (nconc directories 'path)
          )
        )
)

zeus-hammer avatar Nov 06 '20 18:11 zeus-hammer

I'm working on hacking this up on my local copy, but it's inelegant and doesn't take advantage of projectile's caching. I will likely substitute this for #'file-name-directory and flatten the whole list. I can help with this issue as well, I just don't know the best way to go about it as there are multiple routes. My hack would likely be untenable for large projects.

(defun get-dirs-from-path (path)
  (let ((directories '()))
        (while (not (or (equal path "/") (equal path "~")))
          (setq path (file-name-directory (directory-file-name path)))
          (message "path: %s" path)
          (nconc directories 'path)
          )
        )
)

I just realized this won't work, as it'll go all the way up to the root of the fs. You'd need to pass in the project path and go up to that as the root. Either way, you'd almost certainly want to look top down

zeus-hammer avatar Nov 06 '20 18:11 zeus-hammer

Yeah, I agree the approach taken here is a poor one. We can adapt the native indexing function to just collect folders or we can invoke some shell command to retrieve the folders. I guess the command is implemented the way it currently is to simplify to avoid having to deal with a situation where external tools like find might be avaiable.

bbatsov avatar Dec 02 '20 11:12 bbatsov