In his article What’s new in Emacs 27.1? Mickey Petersen wrote that the built-in project.el1 is a library “not widely used” and “about 20 years too late”, concluding that it is “a missed opportunity”.

From what I can tell project.el has been around since 2015. As everything else in our Emacs world, it’s an effort from volunteers offered back to the community. I see no reason to avoid it because it has been lacking the attention of other well-known alternatives such as Projectile. On the contrary, I believe that the more users try it and report what is missing back to the developers or perhaps contribute with lines of code, the more chances this tiny library has to grow and to get better.

Instead of replacing helm-ls-git with project.el, I decided to leave Helm aside for a moment and try my luck with Icomplete. I trust Protesilaos Stavrou’s opinion on it, anyway, so that’s another good point to give it a shot. If you want a detailed setup for Icomplete be sure to follow his lead. I limited myself to installing icomplete-vertical and orderless to make my life easier.

The entry point in project.el is the prefix C-x p . You can hit C-h after that to see the available key bindings. If you press C-x p p a list of known projects will appear. You will see that an option to select a new project is always available, so it’s trivial to add new elements here.

Once you land on your candidate hit RET to be presented with a customizable list of commands to act on the selected project. The keys to activate the commands are highlighted so, for instance, you can press f to start exploring your project files.

The defaults key bindings provided by project.el suit me well enough, but I did apply some changes to tune its behaviour to my needs.

First, I don’t want to rely on find for project--files-in-directory when the faster fd is around the corner.

(el-patch-defun project--files-in-directory (dir ignores &optional files)
  (el-patch-remove
    (require 'find-dired)
    (require 'xref)
    (defvar find-name-arg))
  (let* ((default-directory dir)
         ;; Make sure ~/ etc. in local directory name is
         ;; expanded and not left for the shell command
         ;; to interpret.
         (localdir (file-local-name (expand-file-name dir)))
         (command (el-patch-swap
                    (format "%s %s %s -type f %s -print0"
                            find-program
                            localdir
                            (xref--find-ignores-arguments ignores localdir)
                            (if files
                                (concat (shell-quote-argument "(")
                                        " " find-name-arg " "
                                        (mapconcat
                                         #'shell-quote-argument
                                         (split-string files)
                                         (concat " -o " find-name-arg " "))
                                        " "
                                        (shell-quote-argument ")"))
                              ""))
                    (format "fd -t f -0 . %s" localdir))))
    (project--remote-file-names
     (sort (split-string (shell-command-to-string command) "\0" t)
           #'string<))))

Then I had to tweak project-kill-buffer-conditions for buffers in cider-repl-mode to ensure project-kill-buffers catches them.

(add-to-list 'project-kill-buffer-conditions '(major-mode . cider-repl-mode) t)

The last customization was adding quick keys for magit-status, project-shell, and ripgrep to project-switch-commands. You don’t need my Elisp for that.

As little and work-in-progress as it is, project.el already covers everything I usually need to handle my projects. It’s good to know that right within our beloved text editor comes a tool like this. And no, Pat Benatar, this time is not too little too late.


  1. Note that project.el is also available on ELPA↩︎