Projectile 3.1
Hot on the heels of Projectile 3.0 comes Projectile 3.1!
Three days apart, yes. There’s a story there. A big chunk of what’s in 3.1 was originally meant for 3.0, but 3.0 was already turning into a monster of a release and I decided to cut it into two, so I’d actually be able to reason about each of them. So think of 3.1 less as “the next release” and more as “the second half of 3.0 that I was too scared to ship all at once”.
There was also a bit of calendar mischief involved. I really wanted a release on the 1st of July - that’s July Morning, which is a bit of a thing here in Bulgaria1 - and then another one on the 4th of July. Partly because it’s the 250th US Independence Day, and partly because it happens to be my wedding anniversary. When else am I going to get a round number like that to line up? So here we are.
Unlike 3.0, 3.1 has no breaking changes. Nothing was removed, no command or option changed its name. What it does instead is knock out a pile of long-standing ideas and feature requests, most of which pull in the same direction: making Projectile leaner under the hood and a lot more extensible from your own config.
Projectile learns what you actually work on
projectile-find-file now ranks the files you visit most often and most recently
at the top of the completion list. It’s the kind of thing you don’t notice until
you go back to a Projectile without it and suddenly your muscle memory is off.
It works with any completion UI (Vertico, the default one, whatever) and under
every indexing method, and it’s on by default. If it’s not your thing, set
projectile-enable-frecency to nil.
Named project tasks
The six lifecycle commands (compile, test, run, …) were never enough for real
projects, which tend to accumulate a dozen little “run this incantation”
commands. So there’s projectile-tasks now - an arbitrary set of named commands
you can attach to a project. The nice part is you can put them in
.dir-locals.el and share them with your team through the repo:
((nil . ((projectile-tasks . (("lint" . "make lint")
("deploy" . "make deploy STAGE=prod"))))))
Then s-p c x (projectile-run-task) prompts you for a task and runs it, with a
prefix argument if you want to tweak the command first. This one subsumes about
four separate feature requests I’d been staring at for years.
Finding files by kind
This is my favourite one. Lots of frameworks organize files into well-known
kinds - Rails has models, controllers, views and helpers; Django has models,
views and urls; and so on. Projectile can now describe those kinds
declaratively, and it gives you two commands for free: s-p j to jump to a file
of a particular kind, and s-p J to hop between related files (from a model to
its controller to its views and back).
The best part is that it’s not hardcoded. Rails and Django ship out of the box,
but the whole thing is driven by a :file-kinds entry on the project type, so
you can teach Projectile about your own framework’s layout in a few lines. This
is the “more extensible” theme in a nutshell - I’d much rather ship a mechanism
than a hardcoded list.
Running the test at point
If you’re on Emacs 29+ with tree-sitter, s-p c . runs just the test your cursor
is in, rather than the whole suite. It figures out the enclosing test from the
parse tree and builds the right command. pytest, go test and jest are supported
out of the box, and - you guessed it - you can register rules for other test
runners yourself.
Other-window and other-frame, the Emacs way
Instead of a small army of -other-window/-other-frame command variants, there
are now s-p 4 4 and s-p 5 5 prefixes, modeled exactly on Emacs’s own
C-x 4 4 / C-x 5 5. Press s-p 4 4 and the next Projectile command opens its
buffer in another window - even commands that never had a dedicated variant. The
old commands still work, of course.
More solid under the hood
A few things got quietly sturdier:
- File-notify cache updates.
projectile-invalidate-cachehas always been Projectile’s most notorious footgun. If you opt intoprojectile-auto-update-cache-with-watches, Projectile watches your project and keeps its file cache in sync as files come and go, so you rarely have to think about invalidation at all. It’s off by default and deliberately conservative - when in doubt it just rebuilds. .projectilefinally makes sense. The glob patterns in your dirconfig now follow.gitignore-style rules, and they behave identically undernativeandhybridindexing. This kills a whole family of “why is this file showing up” bug reports that go back the better part of a decade.- TRAMP got faster again. Project and project-type detection now probe marker files with a single directory listing instead of a stat per marker, which turns dozens of sequential round-trips into one over TRAMP.
Wrapping up
There are no breaking changes, but a handful of defaults and behaviors did shift (auto-discovery is on by default now, for one), so give the Upgrading to Projectile 3.1 guide a quick read before you upgrade. The full list of changes lives in the changelog, as always.
With 3.1 out the door, Projectile is basically where I always wanted it to be. I’ve got a few ideas for follow-up releases, but the pressure is off - this is the release where the big items I’d been carrying around for years finally got done. Another win for my burst-driven approach to maintaining my projects: nothing happens for a while, and then a whole lot happens at once.
Keep hacking!
-
July Morning is a Bulgarian tradition where people head to the Black Sea coast to greet the sunrise on the 1st of July. It started as a hippie / rock-and-roll thing in the 80s and stuck around. Highly recommended, if you ever get the chance. ↩