Projectile 3.0 is a big cleanup and modernization release. It removes a pile of long-deprecated or low-value features, folds several families of near-duplicate commands behind pluggable backends, drops support for Emacs 27.1 (the new minimum is Emacs 28.1), and ships a lot of bug fixes.
Because there are breaking changes, please read the Migrating to Projectile 3.0 guide - it walks through everything that changed and how to update an older config.
Changes
- Remove the dedicated
ido/ivy/helmcompletion systems (and theautomode that detectedido-mode/ivy-mode/helm-mode).projectile-completing-readnow uses Emacs'scompleting-read(the formerdefault), which works with Vertico, Consult, Fido, Ido'sido-ubiquitous, etc., and still tags candidates with theproject-filecategory.projectile-completion-systemnow takesdefaultor a custom function; the removed values degrade todefault, so nothing breaks.helm-projectileandcounsel-projectileare unaffected (they don't go through this path). projectile-find-referencesnow honours Projectile's ignore configuration (.projectileand the globally-ignored files/directories) and is scoped via the project's file set, likeprojectile-grep/-ag/-ripgrep. It previously went through the semantic-symref API, which searched the whole tree and ignored that configuration. It also now defaults the prompt from the active region (not just the symbol at point) and no longer carries a dead pre-27 display fallback. The search remains a backend-agnostic textual one; for semantic references use the built-inxref-find-references(which is also scoped to the Projectile project).- Remove the built-in tags support:
projectile-find-tag,projectile-regenerate-tags,projectile-visit-project-tags-table, and theprojectile-tags-command/projectile-tags-backendoptions (along with the ggtags/etags-select special casing). ctags/etags navigation has been largely superseded byxrefand LSP (eglotis built in since Emacs 29). Usexref-find-definitionsdirectly, orprojectile-find-referencesfor project-wide references.projectile-tags-file-nameis kept, since it's still used to exclude a generated tags file from indexing. - Remove
projectile-browse-dirty-projectsandprojectile-vcs-dirty-state. The implementation spun upvc-dirand busy-waited (sleep-for) up to 30 seconds per project across every known project, scraping status strings - slow, blocking, and niche. Use Magit,vc-dir, or a dedicated tool to find projects with uncommitted changes. - Remove the idle timer (
projectile-enable-idle-timer,projectile-idle-timer-seconds,projectile-idle-timer-hook). It existed mainly to re-runprojectile-regenerate-tagson an idle timer, which makes little sense in an LSP/xref world, and it was off by default. Use a plainrun-with-idle-timerif you really want this behavior. - Remove
projectile-commander(anddef-projectile-commander-method), the single-key command dispatcher. It's superseded byprojectile-dispatch, thetransientmenu added in this cycle. The kbd:[s-p m] binding and theC-u s-p pproject-switch prefix now invokeprojectile-dispatchinstead (the prefix falls back toprojectile-switch-project-actionwhentransientisn't installed). - A cold
projectile-find-file(and any command that lists project files) no longer freezes Emacs while the project is indexed under thealien/hybridmethods. The indexing command runs asynchronously and is awaited in a way that keeps Emacs responsive to redisplay andC-g(which aborts the indexing), instead of blocking until it finishes. The resulting file list is identical; only the responsiveness during indexing differs. Controlled by the newprojectile-async-indexing(default on); it has no effect undernativeindexing, in batch mode, or while a keyboard macro runs, all of which index synchronously as before. - Speed up native indexing further:
projectile-index-directorynow reads each directory withdirectory-files-and-attributes, so an entry's type comes from the listing call instead of afile-directory-pstat per file. That stat was a separate filesystem round-trip each, which dominated the walk on large and remote (TRAMP) trees - it's now one round-trip per directory instead of one per file. Symlinks pointing at directories are still followed, matching the previous behaviour. Roughly 40% faster on a local 12k-file tree; the win is far larger over TRAMP. - Speed up native indexing's post-walk step:
projectile-dir-files-nativestrips the project-root prefix with a singlesubstringper file instead offile-relative-name, which paid for anexpand-file-name/abbreviate-file-nameper file.projectile-project-filesalso skips re-relativising the listing when the only directory walked is the project root itself (the common single-directory case). Together that removes roughly two seconds of overhead per 90k files indexed. - #1872: Clarify in the
projectile-register-project-typedocstring and the manual that a list ofmarker-filesmust all be present for a type to match (logical AND), and that a predicate function should be used to match when any one of several files is present. - #1935: The
emacs-easkproject type now has atestcommand (eask test), soprojectile-test-projectworks out of the box for Eask projects. Eask auto-detects the test framework (buttercup, ERT, ...), so no framework-specific type is needed. - #1638: Document in
projectile-svn-commandthat it runs non-interactively and therefore needs SVN credentials to be cached up front for authenticated remotes. - Keep a separate command history per lifecycle command type (configure, compile, test, install, package, run), so the prompt's
M-phistory no longer mixes, say, test commands with compile commands.projectile-repeat-last-commandstill uses the combined per-project history and is unaffected. - Remove the unused private
projectile--init-known-projectsalias (a leftover compatibility shim for the old known-projects API; nothing in the codebase referenced it). projectile-get-immediate-sub-projectsskips thegit submodule foreachshell-out for git projects with no.gitmodulesfile anywhere up the parent chain. Hot path for monorepos that index the project root often.projectile-discover-projects-in-directorynow usesdirectory-files-no-dot-files-regexpto skip.and..at the C level instead of doing the post-filter in Elisp - matches the indexing walker.- Document the anchored vs
*-prefixed semantics ofprojectile-globally-ignored-directories, thefindfallback's lack of common directory exclusions whenfdisn't available, and howfd/git ls-fileshandle deleted-but-unstaged files differently. - Speed up native indexing on large trees:
projectile-index-directorynow hashes the ignored-files / ignored-directories / globally-ignored-directory-names lists once per indexing call (the per-filemember' scans were O(N*M)), expands dirconfig glob patterns once per directory level instead of once per (file, pattern) pair, and accumulates results into a shared cell so we no longer pay for anapply append' at each recursion level. projectile-remove-ignored(hybrid post-processing) now hashes the ignored-files basenames and pre-splits ignored-dirs into prefix-match and any-segment groups, so the per-file inner loops drop from O(M)seq-somewalks to O(1) hash lookups (or O(segments) for*-prefixed entries).- Hybrid indexing now batches the external command into a single invocation when the project's
.projectiledeclares multiple+keep entries, instead of shelling out once per kept subdirectory. The kept paths are passed to the indexing tool (e.g.git ls-files,fd,find) as positional pathspecs and submodule files outside those subdirectories are filtered out. Resolves the long-standing TODO inprojectile-project-files. projectile-files-via-ext-commandnow accepts an optionalpathspecsargument; entries are shell-quoted before being appended to the command.projectile-dir-files-aliensimilarly accepts an optionalsubdirsargument that threads through.- Document the
hybridindexing method in the manual and add a feature matrix showing which Projectile knobs (dirconfig, global ignores/unignores, sort order, default caching) apply undernative/hybrid/alien. projectile-dir-files-aliennow accepts an optionalvcsargument so the dispatcher can thread through the already-resolved VCS instead of recomputing it. Existing single-argument callers are unaffected.projectile-index-directory(native indexing) now relies ondirectory-files-no-dot-files-regexpto filter out.and..at the C level instead of walking past them in Elisp.projectile-project-root-cachenow keys entries on cons cells ((FUNC . DIR)for per-function results,('none . DIR)for the overall failure marker) instead of formatted strings. This is internal state, but third-party code that reaches into the cache directly will need to update.projectile-parse-dirconfig-file' now returns aprojectile-dirconfig' struct (withkeep',ignore',ensure', andprefixless-ignore' slots) instead of a positional 3-tuple. External callers should use the accessors (projectile-dirconfig-keep' etc.) rather thancar'/cadr'/caddr'.- Soft-deprecate prefix-less ignore entries in
.projectile'. Lines without a+'/-'/!' prefix are still treated as ignore patterns for backward compatibility, but a one-time warning is now shown for each project that uses them. Set `projectile-warn-on-prefixless-dirconfig-lines' to nil to silence. - [Breaking] Remove the legacy single-letter lifecycle keybindings
C/K/L/P/u(configure/package/install/test/run project). Use thecprefix instead (c o,c p,c i,c t,c r). - Remove two long-obsolete aliases:
projectile-global-mode(useprojectile-mode) andprojectile-project-root-files-functions(useprojectile-project-root-functions). - Pre-compute file timestamps in
projectile-sort-by-modification-timeandprojectile-sort-by-access-timeto reduce stat calls from O(n log n) to O(n). - Cache
projectile-parse-dirconfig-fileresults per project root (invalidated by file modification time), avoiding redundant file reads during indexing. - Cache
file-truenameresults inprojectile-project-buffer-pwhen checking multiple buffers, reducing redundant symlink resolution. - Use a hash set for deleted file removal in
projectile-dir-files-alien, improving performance from O(n*m) to O(n+m) when filtering deleted-but-unstaged files. - Avoid redundant
projectile-project-rootcall inprojectile-detect-project-typeby passing through the already-resolved root. - Share
file-truenamecache across buffers inprojectile-open-projects, matching the optimization already inprojectile-project-buffers. - Remove unnecessary temp buffer creation in
projectile--cache-project-commands-p(was creating a buffer and re-reading.dir-locals.elon every compile/test/run invocation). - [Breaking] Bump the minimum required Emacs version to 28.1 (from 26.1). This removes ~30 lines of compatibility code (fileloop fallback,
time-convertfallback,projectile-flattenshim), makestransient(and thereforeprojectile-dispatch) an unconditional dependency, and fixes thetags-query-replaceFIXME inprojectile-replace-regexp. - Add
compatas a dependency, enabling the use of modern Emacs APIs (e.g.string-replace) on older Emacs versions. - Replace most
cl-libsequence functions withseq.elequivalents (seq-filter,seq-remove,seq-some,seq-find,seq-sort,seq-every-p,seq-difference) and convertcl-casetopcase. - #1971: Support
slnxfiles for dotnet project types. - #1958: Exclude
.projectile-cache.eldfrom search results (ripgrep/ag/grep) by default. - #1947:
projectile-project-nameshould be marked as safe. - Set
projectile-auto-discovertonilby default (to avoid startup slowdowns in some situations). - #1943: Consider
projectile-indexing-methodto be safe as a dir-local variable if it is one of the preset values. - #1936: Do not require selecting a project when using
M-x projectile-invalidate-cache, since there is a global cache that is also cleared by that command, even when not operating on any specific project. - Add
build.millas an alternative project marker for the Mill project type, matching Mill's current recommended file extension. - Replace obsolete
when-letandcl-gensymwithwhen-let*andgensymfor compatibility with Emacs 31+.
New features
- #1697: Add
projectile-add-and-switch-project, which prompts for a directory, adds it to the known projects, and immediately switches to it. - #1698: Support
%pin project command strings (configure/compile/test/run/install/package); it is replaced with the project name when the command runs, so e.g.:run "docker run %p"works for both registered project types and interactively entered commands. - Add
projectile-run(kbd:[s-p x r]), a single command that opens a shell, REPL or terminal in the project root over a pluggable backend (the same registry that powersprojectile-search). Projectile shipsshell,eshell,ielm,term,vterm,eatandghostelbackends;projectile-shell-backendselects which is used (defaulteshell). Register your own terminal withprojectile-register-shell-backend. The dedicated commands (projectile-run-eshell,projectile-run-vterm, ..., and the-other-windowvariants) are kept as thin wrappers that force a backend. - Add
projectile-search(kbd:[s-p s s]), a single search command that runs over a pluggable backend. Projectile shipsgrep,ripgrepandagbackends;projectile-search-backendselects which is used (autoby default, favouring ripgrep then grep). Register your own tool (deadgrep, consult-ripgrep, ...) withprojectile-register-search-backend.projectile-grep,projectile-ripgrepandprojectile-agare kept as thin wrappers that force a specific backend. Note the keymap change: kbd:[s-p s s] is nowprojectile-search(it used to beprojectile-ag), andprojectile-agmoves to kbd:[s-p s a]. The generic registry behind this (projectile-register-backend) is designed to be reused for other command families later. projectile-dispatchnow exposes command modifiers astransientswitches. A Modifiers group offers--invalidate-cache(-i, rebuild the file cache first, for the find file/dir commands),--regexp(-r, forag/ripgrep),--new-process(-n, for the shells/REPLs), and--display(-d, cycle the display target through this window / other window / other frame, for the file/buffer/project commands). The--displayswitch replaces the old dedicated "Other window" and "Other frame" menu columns. (Theprojectile-command-map4 <key>/5 <key>bindings are unchanged.)- Add
projectile-consult.el, an optional Consult integration distributed alongside Projectile (with a soft dependency onconsult, so it's only loaded if yourequireit).projectile-consult-find-filedrives a streaming Consult finder from Projectile's own indexing command (viaprojectile-project-files-producer), so candidates appear in the minibuffer as the project is indexed instead of blocking until it's done, while still honouring the project's VCS and indexing configuration. The module is self-contained so it can be split into its own package later; it requires Emacs 29.1+ (Consult's floor). - Add
projectile-index-project-async, which indexes the current project in the background (viamake-process) and populates the files cache without freezing Emacs, so a laterprojectile-find-filefinds the cache already warm. Works with thealien/hybridindexing methods (thenativeElisp walk can't run off the main thread) and requires caching to be enabled. The underlying building blocks -projectile-files-via-ext-command-asyncandprojectile-dir-files-alien-async- are public, so a streaming finder (e.g. one built onconsult) can drive Projectile's indexing command asynchronously. Remote projects are handled via TRAMP. The result is identical to the synchronous indexing path. - Add
projectile-project-files-producer, which returns a plist (:directory,:vcs,:command,:separator) describing how to list a project's files with its external indexing command. It gives an external asynchronous/streaming file finder (e.g. one built onconsultoraffe) a single, stable entry point for driving Projectile's own indexing command instead of stitching together the root, VCS and command builders by hand. - #1956: Add
projectile-switch-project-other-windowandprojectile-switch-project-other-frame(bound to4 pand5 p), which switch to a project and display it in another window/frame. The action they run is configurable viaprojectile-switch-project-other-window-action/projectile-switch-project-other-frame-action(find-file in the other window/frame by default). - #1809: Track the project switched away from in
projectile-most-recent-projectand addprojectile-switch-to-most-recent-projectto jump back to it (repeated calls toggle between the two). Only switches made through Projectile's switch-project commands are tracked. - #1861: Add
projectile-discard-command-cacheto drop the cached configure/compile/test/install/package/run commands for the current project, so a freshly edited.dir-locals.el(or project-type default) is picked up on the next run. Can be called manually or added toafter-save-hook. - Add
projectile-uniquify-dirname-transform, a project-aware value foruniquify-dirname-transformthat disambiguates same-named buffers using the project name. Mirrorsproject.el'sproject-uniquify-dirname-transform. - Add
projectile-dispatch, atransientmenu mirroringprojectile-command-mapfor more discoverable command access.transientis an optional dependency (it requires Emacs 28+); the menu is only available when it's installed and is not bound to a key by default. - #2008: Add
projectile-remove-project-typeto unregister a project type. This is the supported way to stop Projectile auto-detecting a type; clearing itsmarker-filesdoes not work (see the related bug fix below). - #1936: Add
projectile-discard-root-cachecommand to clearprojectile-project-root-cachewithout touching other Projectile caches. Useful after creating or removing a project marker, since the existingprojectile-invalidate-cacheeither also drops the file list cache or prompts for a project depending on context. - Warn once per session when
projectile-indexing-method' isalien' but the project has a non-empty.projectile' file, so users notice their dirconfig rules are being bypassed. Controlled by the newprojectile-warn-when-dirconfig-is-ignored' option. - Warn when a
+' keep entry in.projectile' contains glob metacharacters. The `+' prefix is for subdirectory paths only and globs are silently coerced into a non-matching directory name; the warning surfaces the misuse rather than letting it fail silently. - #1964: Implement
project-nameandproject-buffersmethods for theproject.elintegration, so that code usingproject.elAPIs returns correct results for Projectile-managed projects. - Implement the
project-ignoresmethod for theproject.elintegration, translating Projectile's global ignores, ignored file suffixes, and dirconfig (.projectile) ignore entries into the glob formatproject.elexpects. This makes Projectile a more completeproject.elbackend for tools that rely on the protocol (e.g.project-find-regexp). - Add
projectile-forget-projects-underto drop all known projects located under a directory (with a prefix argument it recurses into nested projects). Mirrorsproject.el'sproject-forget-projects-under. - Add
projectile-forget-zombie-projectsas an alias forprojectile-cleanup-known-projects, for discoverability and parity withproject.el'sproject-forget-zombie-projects. projectile-kill-buffers-filternow also accepts a composable list of conditions (buffer-name regexps, predicates, andmajor-mode/derived-mode/not/and/orforms), modeled onproject.el'sproject-kill-buffer-conditions. The existingkill-all,kill-only-files, and predicate-function values keep working unchanged.- #1837: Add
eatproject terminal commands with keybindingsx xandx 4 x. - Add
ghostelproject terminal commands with keybindingsx Gandx 4 G. - Add keybinding
A(in the projectile command map) and a menu entry forprojectile-add-known-project. - #1653: Add
projectile-compile-subprojectandprojectile-test-subprojectcommands for building/testing individual modules in multi-module projects (e.g. Maven, Gradle). Bound toc m candc m t.
Bugs fixed
- #2005: Fix hybrid indexing with
fdwhen the.projectilefile has+keep entries. The kept directories were appended to thefdcommand as bare positional arguments, butfd's grammar is[pattern] [path...](so the first path was misread as the search pattern) andfd9+ additionally rejects--strip-cwd-prefixalongside explicit paths (error: ... '--strip-cwd-prefix' cannot be used with '[path]...'). Projectile now passes the directories tofdvia--search-pathand drops--strip-cwd-prefixin that case. Other tools (git ls-files,find, etc.) still get plain trailing paths. - #2042: Asynchronous indexing now runs its command under
/bin/shrather than the user's interactiveshell-file-name. The async runner wraps the command as{ ...; } 2>file, which is POSIX-sh syntax and broke for users whose shell iscsh/tcsh/fish(it produced bogus{-related output instead of a file list). The indexing command itself is a plain POSIX command, so a POSIX shell is the correct interpreter regardless of the login shell. - #2046: Commands chosen from the
projectile-dispatchmenu now run in the project that was just selected, instead of the current buffer's project. The menu is atransient, so its suffix commands run after the project switch has unwound; the switched-to project is now kept current for the lifetime of the menu and restored when it exits. This applies whether the menu is reached viaC-u s-p por by settingprojectile-switch-project-actiontoprojectile-dispatch(or any other transient). - #2037:
projectile-consult.elnow byte-compiles cleanly whenconsultisn't installed. Because the module ships inside the Projectile MELPA package, build systems (package.el, nixpkgs) try to compile it withoutconsultpresent, which previously errored on its top-level(require 'consult).consultis now loaded softly at the top (and hard-required inside the command, where it's actually needed), with the used functions forward-declared. - #2042: A non-zero exit from the indexing command (
fd,git ls-files,find, ...) no longer abortsprojectile-find-filewhen the command still produced a file listing. External listers likefdroutinely exit non-zero on benign conditions (e.g. an unreadable directory hit mid-traversal); that output is now used. Auser-erroris still raised when a non-zero exit produced no output at all, so a genuinely broken/missing command is still surfaced rather than mistaken for an empty project. - #1663: Projects matched by
projectile-ignored-projects(orprojectile-ignored-project-function) are now excluded fromprojectile-relevant-known-projects, so they no longer show up inprojectile-switch-projecteven when they were added to the known projects before being ignored. Previously the ignore list was only consulted when adding a project. - #1909: A project type registered with a predicate
marker-filesfunction now receives the project root as its argument when detecting the current project's type. Previously it was passednil, so such a function could never match the current project. - #1829:
projectile-project-rootno longer errors with(wrong-type-argument stringp nil)whendefault-directoryis nil (which can happen in some non-file buffers); it returns nil instead. - #1946:
projectile-ripgrepnow builds its ignore exclusions as--glob=!PATTERNinstead of--glob '!PATTERN'. The surrounding single quotes were only stripped by POSIX shells, so on Windowscmdthey became part of the pattern and the exclusions silently failed. - #2008: A project type with an empty
marker-fileslist no longer matches every project.projectile-verify-filesis vacuously true for an empty list, so a type whose markers were cleared (e.g. viaprojectile-update-project-type ... :marker-files nil) would be detected everywhere instead of nowhere.projectile-detect-project-typenow treats an empty marker set as a non-match. projectile-discard-root-cache' andprojectile-invalidate-cache' now also clearprojectile-file-exists-cache'. Without this, after creating a new project marker (.projectile',.git', etc.) over TRAMP, the negative entries cached during earlier root-walks would keep reporting "not found" for up toprojectile-file-exists-remote-cache-expire' seconds even after the user explicitly invalidated the root cache.projectile-find-file-hook-functionno longer disables all of Projectile for remote buffers - the cheap operations (file caching, known-projects tracking, project buffer-count cap) now run regardless of remoteness, and only the genuinely slow ones (mode-line update, tags-table visit) stay gated. Previously the entire hook was a single `(unless (file-remote-p ...))', which meant remote projects never showed up in known-projects auto-tracking and never had their files cached on visit.- Stop stat'ing remote known projects in
projectile-keep-project-p. The "remote and connected" branch usedfile-readable-p, which is a remote round-trip per project; withprojectile-auto-cleanup-known-projects' enabled, that turned every project switch into a serial network walk over all remote known projects. Remote projects are now always kept; users can drop dead ones withprojectile-remove-known-project'. - Skip
file-truenamefor remote paths inprojectile-ignored-project-pandprojectile-ignored-projects. The known-projects tracker callsprojectile-ignored-project-pfrom the find-file hook for every visit, and the previous behavior round-tripped to the remote on each call (and once per ignored entry) just to canonicalize symlinks. projectile-project-buffer-pnow skips thefile-truenamecall for buffers visiting remote (TRAMP) files. Each truename was a remote round-trip andprojectile-project-buffers' iterates the fullbuffer-list', so projects with N remote buffers in distinct directories were paying N round-trips on every call. Resolving symlinks across a TRAMP boundary is rarely meaningful in any case.- Replace the 10 sequential
file-exists-pprobes inprojectile-project-vcswith a singledirectory-fileslisting, and cache the per-project result inprojectile-project-vcs-cache. Also collapse the up-the-tree dominator walk so each ancestor directory is listed at most once instead of up to 10 times. Over TRAMP this turns "1 round-trip per VCS marker per probed directory" into "1 round-trip per probed directory", and the cache means follow-up callers (the indexing path,projectile-project-info, the project-cleanup loop) hit memory. - Cut down
projectile-project-rootround-trips on TRAMP. The cache-validity check used rawfile-exists-pon the cached project root, which is a fresh remote stat on every call; switched toprojectile-file-exists-pso the per-host TTL applies. Also memoize(file-truename dir)across the `projectile-project-root-functions' loop so cache-miss calls compute the truename once instead of once per probe (4-5 remote stats with the default function list). - #1501, #1275: Detect
fd/fdfindon the remote host for TRAMP projects instead of reusing the locally-detectedprojectile-fd-executable. The locally-resolved value is meaningless on the remote, so whenfdwas installed on the local box but not on the remote (or vice-versa), git indexing would silently fall back to an empty file list. The newprojectile-fd-executable-forreturns the per-directory executable, with a per-host cache so the remoteexecutable-findlookup runs at most once per host per session. - #1898, #1932: Surface non-zero exit codes from the indexing command in
projectile-files-via-ext-commandinstead of silently returning an empty file list. The most common cause isfd/gitbeing installed locally but not on the remote host of a TRAMP project — previously this manifested as an unexplained empty completion list (orstringp, nilcrashes downstream); it now signals auser-errorpointing at the*projectile-files-errors*buffer for the captured stderr. - #1211: Fix file-local
projectile-project-rootoverrides being ignored after the first buffer in a directory was visited. The cache used(default-directory)as part of the key butprojectile-root-localreads a buffer-local variable, so two buffers in the same directory with different overrides got the first buffer's answer. Results fromprojectile-root-localare no longer cached. - #1836: Memoize per-function
nilresults in the project root cache. Previously, when an early entry inprojectile-project-root-functionsreturned nil, the nil was indistinguishable from a missing cache entry, so the function was re-run on everyprojectile-project-rootcall. With many entries this could cost several full directory walks per call. A'nonesentinel is now stored for unsuccessful entries. - #1508: Fix dirconfig parser silently treating lines as ignore patterns when the
+/-/!prefix or the comment character is preceded by whitespace; leading spaces and tabs are now skipped before prefix dispatch. - Fix
projectile-files-via-ext-commandexecuting empty string as shell command for non-git VCS sub-projects. - Fix
projectile-select-filescrashing on filenames with regexp metacharacters by usingstring-searchinstead ofstring-match. - Fix
projectile-find-referencesusing internalxref--show-xrefsAPI whose signature changed across Emacs versions. - Fix
projectile-determine-find-tag-fnfalling back tofind-tagwhich was removed in Emacs 29; now falls back toxref-find-definitions. - Fix
projectile-files-to-ensureexpanding wildcards relative to the current buffer instead of the project root. - Fix
projectile-ignored-project-pfailing to match abbreviated paths against truename-resolved ignored projects list. - Fix
projectile-edit-dir-localssaving partial.dir-locals.elcontent when the user aborts skeleton insertion withC-g. - Fix
projectile--other-extension-filessort comparator ignoring its second argument, producing undefined ordering; replaced with a stable partition. - Fix
projectile-toggle-project-read-onlyoperating on the wrong buffer afteradd-dir-local-variableby wrapping insave-selected-window. - Fix
projectile-cache-current-filecallingprojectile-project-roottwice instead of reusing the already-resolved value. - Fix
projectile-cache-current-filequeueing one idle timer per opened file, each capturing a stale snapshot of the file list. With persistent caching, opening many files in a session would result in N redundant disk writes after Emacs went idle. A pending flush is now coalesced per project and reads the latest in-memory cache at fire time. - Fix
projectile-load-project-cachenot recording a cache time, which combined withprojectile-files-cache-expiremade the TTL check immediately re-evict freshly loaded data — every call ended up re-reading the cache file from disk and the data was never reindexed. The cache file's mtime is now used to seedprojectile-projects-cache-time. - Fix
projectile-load-project-cachestoring nil in cache on corrupt/empty cache files, preventing future reload attempts. - Fix
projectile-purge-dir-from-cacheonly updating the in-memory cache; with persistent caching the purged directory's files would reappear on the next session. The on-disk cache is now updated as well, matching the behavior ofprojectile-purge-file-from-cache. projectile-invalidate-cachenow deletes the persistent cache file instead of overwriting it with a serializednil.- Fix
projectile--cmake-command-presetsusingmapcarinstead ofmapcan, producing nested lists for included presets. - Fix
projectile--eatignoring thenew-processargument when generating buffer names. - Fix
projectile-check-vcs-statushanging indefinitely by adding a 30-second timeout to its busy-wait loop. - Fix
projectile-sort-by-modification-timeandprojectile-sort-by-access-timecrashing on deleted files (nilfile-attributes). - Fix
projectile-recentf-filesfailing to match files when the project root contains symlinks. - Fix
projectile--run-project-cmdpassing nil tocompilewhen no command is configured andcompilation-read-commandis nil — now signals a clearuser-error. - Fix
projectile--merge-related-files-fnsusing destructivenconcwhich could corrupt shared data and silently drop values when the existing list was nil. - Fix
projectile-configure-commandformat call passing an unusedcompile-dirargument. - Fix
projectile-update-project-typeresetting the project type cache with wrong hash table:test(used defaulteqlinstead ofequalfor string keys). - Fix
projectile-find-file-hook-functionrunningprojectile-maybe-limit-project-file-bufferson TRAMP buffers, causing potential hangs on slow connections. - Use
expand-file-nameinstead offile-truenameinprojectile-compilation-dirto avoid unnecessary symlink resolution (and TRAMP network round-trips). - Use non-destructive
appendinstead ofnconcinprojectile-get-all-sub-projectsto avoid mutating caller data. - Add
safe-local-variablepredicates for project settings variables (projectile-project-test-suffix,projectile-project-compilation-cmd, etc.) so they can be set in.dir-locals.elwithout prompting. - #1962: Fix
projectile-get-other-filescrashing when a candidate other-file lives in the project root directory. - #1816: Fix
projectile-expand-file-name-wildcardfailing when a parent directory is not readable (e.g. iCloud Drive, Termux). - #1841: Preserve user's
compilation-buffer-name-functionwhenprojectile-per-project-compilation-bufferis nil. - #1823: Update the mode-line via
window-configuration-change-hookso non-file buffers (e.g. Magit) display the correct project info. - #1886: Fix
(wrong-type-argument stringp nil)error when running project commands in a newly created project by usingprojectile-acquire-rootinstead ofprojectile-project-rootinprojectile--run-project-cmd. - #1456: Fix
projectile-replace-regexpstopping when encountering a missing file by filtering nonexistent files from the replacement file list. - #1687: Fix
projectile-grep-finished-hooknot running on subsequent grep calls due to buffer rename collision. - #1939: Handle corrupted
projectile-known-projects-filegracefully instead of crashing with(wrong-type-argument listp ...). - #1748: Fix
projectile-replacefalling back to the legacy Emacs 25/26 code path on Emacs 27+ becausefileloopwas not loaded. - #1741: Fix
projectile-replacetreating the search string as a regexp instead of a literal string on Emacs 27+. - #1729: Fix
projectile-root-top-downto actually return the topmost matching project root instead of the bottommost. - #1596:
projectile-find-dirnow includes intermediate directories that contain only subdirectories (e.g.src/when it only hassrc/ComponentA/,src/ComponentB/). - #1551: Don't add nonexistent files to the project cache (e.g. when visiting a new file with
find-fileand then abandoning the buffer). - #1554: Fix
projectile-files-with-stringfailing on special characters when usinggreporgit-grepby adding the-F(fixed-string) flag. - #1897: Filter deleted-but-unstaged files from
git ls-filesoutput in alien/hybrid indexing (whenfdis not used). - #1873: Skip unreadable directories during native indexing instead of aborting with a permission error.
- #1961: Prevent directories from matching file-type project root markers (e.g., a
workspacedirectory no longer matches theWORKSPACEBazel marker on case-insensitive filesystems). - #1749: Strip
./prefix fromfdoutput inprojectile-files-via-ext-command, fixing compatibility with olderfdversions that don't support--strip-cwd-prefix. - Fix
projectile-ripgrepfailing on zsh when ignored file patterns contain glob characters, by quoting--globarguments. - Fix CMake version parsing failing when
cmake --versionoutput contains extra text after the version number. - Fix Jujutsu file listing to use template syntax for null-byte-separated output, making it robust against user customization of
jjoutput format. - Fix
projectile-purge-file-from-cacheserializing the stale file list to disk instead of the updated one. - Fix misplaced paren in
projectile-project-buffers-other-buffercausingswitch-to-bufferarguments to be ignored. - Fix
projectile-default-generic-commandsilently dropping lambda/closure commands (only symbol commands worked). - Fix
dired-before-readin-hookbeing added as buffer-local instead of global inprojectile-mode, so it now correctly fires in all dired buffers. - Fix
projectile-find-dir-hooknot being cleaned up when disablingprojectile-mode. - Use
display-warninginstead ofmessagewhen cache serialization fails, making the failure visible in the*Warnings*buffer.