Skip to content

v3.0.0

Latest

Choose a tag to compare

@bbatsov bbatsov released this 01 Jul 17:10

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/helm completion systems (and the auto mode that detected ido-mode/ivy-mode/helm-mode). projectile-completing-read now uses Emacs's completing-read (the former default), which works with Vertico, Consult, Fido, Ido's ido-ubiquitous, etc., and still tags candidates with the project-file category. projectile-completion-system now takes default or a custom function; the removed values degrade to default, so nothing breaks. helm-projectile and counsel-projectile are unaffected (they don't go through this path).
  • projectile-find-references now honours Projectile's ignore configuration (.projectile and the globally-ignored files/directories) and is scoped via the project's file set, like projectile-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-in xref-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 the projectile-tags-command/projectile-tags-backend options (along with the ggtags/etags-select special casing). ctags/etags navigation has been largely superseded by xref and LSP (eglot is built in since Emacs 29). Use xref-find-definitions directly, or projectile-find-references for project-wide references. projectile-tags-file-name is kept, since it's still used to exclude a generated tags file from indexing.
  • Remove projectile-browse-dirty-projects and projectile-vcs-dirty-state. The implementation spun up vc-dir and 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-run projectile-regenerate-tags on an idle timer, which makes little sense in an LSP/xref world, and it was off by default. Use a plain run-with-idle-timer if you really want this behavior.
  • Remove projectile-commander (and def-projectile-commander-method), the single-key command dispatcher. It's superseded by projectile-dispatch, the transient menu added in this cycle. The kbd:[s-p m] binding and the C-u s-p p project-switch prefix now invoke projectile-dispatch instead (the prefix falls back to projectile-switch-project-action when transient isn't installed).
  • A cold projectile-find-file (and any command that lists project files) no longer freezes Emacs while the project is indexed under the alien/hybrid methods. The indexing command runs asynchronously and is awaited in a way that keeps Emacs responsive to redisplay and C-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 new projectile-async-indexing (default on); it has no effect under native indexing, in batch mode, or while a keyboard macro runs, all of which index synchronously as before.
  • Speed up native indexing further: projectile-index-directory now reads each directory with directory-files-and-attributes, so an entry's type comes from the listing call instead of a file-directory-p stat 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-native strips the project-root prefix with a single substring per file instead of file-relative-name, which paid for an expand-file-name/abbreviate-file-name per file. projectile-project-files also 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-type docstring and the manual that a list of marker-files must 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-eask project type now has a test command (eask test), so projectile-test-project works 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-command that 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-p history no longer mixes, say, test commands with compile commands. projectile-repeat-last-command still uses the combined per-project history and is unaffected.
  • Remove the unused private projectile--init-known-projects alias (a leftover compatibility shim for the old known-projects API; nothing in the codebase referenced it).
  • projectile-get-immediate-sub-projects skips the git submodule foreach shell-out for git projects with no .gitmodules file anywhere up the parent chain. Hot path for monorepos that index the project root often.
  • projectile-discover-projects-in-directory now uses directory-files-no-dot-files-regexp to skip . and .. at the C level instead of doing the post-filter in Elisp - matches the indexing walker.
  • Document the anchored vs *-prefixed semantics of projectile-globally-ignored-directories, the find fallback's lack of common directory exclusions when fd isn't available, and how fd/git ls-files handle deleted-but-unstaged files differently.
  • Speed up native indexing on large trees: projectile-index-directory now hashes the ignored-files / ignored-directories / globally-ignored-directory-names lists once per indexing call (the per-file member' 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 an apply 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-some walks 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 .projectile declares 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 in projectile-project-files.
  • projectile-files-via-ext-command now accepts an optional pathspecs argument; entries are shell-quoted before being appended to the command. projectile-dir-files-alien similarly accepts an optional subdirs argument that threads through.
  • Document the hybrid indexing method in the manual and add a feature matrix showing which Projectile knobs (dirconfig, global ignores/unignores, sort order, default caching) apply under native/hybrid/alien.
  • projectile-dir-files-alien now accepts an optional vcs argument 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 on directory-files-no-dot-files-regexp to filter out . and .. at the C level instead of walking past them in Elisp.
  • projectile-project-root-cache now 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 a projectile-dirconfig' struct (with keep', ignore', ensure', and prefixless-ignore' slots) instead of a positional 3-tuple. External callers should use the accessors (projectile-dirconfig-keep' etc.) rather than car'/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 the c prefix instead (c o, c p, c i, c t, c r).
  • Remove two long-obsolete aliases: projectile-global-mode (use projectile-mode) and projectile-project-root-files-functions (use projectile-project-root-functions).
  • Pre-compute file timestamps in projectile-sort-by-modification-time and projectile-sort-by-access-time to reduce stat calls from O(n log n) to O(n).
  • Cache projectile-parse-dirconfig-file results per project root (invalidated by file modification time), avoiding redundant file reads during indexing.
  • Cache file-truename results in projectile-project-buffer-p when 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-root call in projectile-detect-project-type by passing through the already-resolved root.
  • Share file-truename cache across buffers in projectile-open-projects, matching the optimization already in projectile-project-buffers.
  • Remove unnecessary temp buffer creation in projectile--cache-project-commands-p (was creating a buffer and re-reading .dir-locals.el on 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-convert fallback, projectile-flatten shim), makes transient (and therefore projectile-dispatch) an unconditional dependency, and fixes the tags-query-replace FIXME in projectile-replace-regexp.
  • Add compat as a dependency, enabling the use of modern Emacs APIs (e.g. string-replace) on older Emacs versions.
  • Replace most cl-lib sequence functions with seq.el equivalents (seq-filter, seq-remove, seq-some, seq-find, seq-sort, seq-every-p, seq-difference) and convert cl-case to pcase.
  • #1971: Support slnx files for dotnet project types.
  • #1958: Exclude .projectile-cache.eld from search results (ripgrep/ag/grep) by default.
  • #1947: projectile-project-name should be marked as safe.
  • Set projectile-auto-discover to nil by default (to avoid startup slowdowns in some situations).
  • #1943: Consider projectile-indexing-method to 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.mill as an alternative project marker for the Mill project type, matching Mill's current recommended file extension.
  • Replace obsolete when-let and cl-gensym with when-let* and gensym for 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 %p in 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 powers projectile-search). Projectile ships shell, eshell, ielm, term, vterm, eat and ghostel backends; projectile-shell-backend selects which is used (default eshell). Register your own terminal with projectile-register-shell-backend. The dedicated commands (projectile-run-eshell, projectile-run-vterm, ..., and the -other-window variants) 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 ships grep, ripgrep and ag backends; projectile-search-backend selects which is used (auto by default, favouring ripgrep then grep). Register your own tool (deadgrep, consult-ripgrep, ...) with projectile-register-search-backend. projectile-grep, projectile-ripgrep and projectile-ag are kept as thin wrappers that force a specific backend. Note the keymap change: kbd:[s-p s s] is now projectile-search (it used to be projectile-ag), and projectile-ag moves 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-dispatch now exposes command modifiers as transient switches. A Modifiers group offers --invalidate-cache (-i, rebuild the file cache first, for the find file/dir commands), --regexp (-r, for ag/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 --display switch replaces the old dedicated "Other window" and "Other frame" menu columns. (The projectile-command-map 4 <key> / 5 <key> bindings are unchanged.)
  • Add projectile-consult.el, an optional Consult integration distributed alongside Projectile (with a soft dependency on consult, so it's only loaded if you require it). projectile-consult-find-file drives a streaming Consult finder from Projectile's own indexing command (via projectile-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 (via make-process) and populates the files cache without freezing Emacs, so a later projectile-find-file finds the cache already warm. Works with the alien/hybrid indexing methods (the native Elisp walk can't run off the main thread) and requires caching to be enabled. The underlying building blocks - projectile-files-via-ext-command-async and projectile-dir-files-alien-async - are public, so a streaming finder (e.g. one built on consult) 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 on consult or affe) 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-window and projectile-switch-project-other-frame (bound to 4 p and 5 p), which switch to a project and display it in another window/frame. The action they run is configurable via projectile-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-project and add projectile-switch-to-most-recent-project to 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-cache to 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 to after-save-hook.
  • Add projectile-uniquify-dirname-transform, a project-aware value for uniquify-dirname-transform that disambiguates same-named buffers using the project name. Mirrors project.el's project-uniquify-dirname-transform.
  • Add projectile-dispatch, a transient menu mirroring projectile-command-map for more discoverable command access. transient is 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-type to unregister a project type. This is the supported way to stop Projectile auto-detecting a type; clearing its marker-files does not work (see the related bug fix below).
  • #1936: Add projectile-discard-root-cache command to clear projectile-project-root-cache without touching other Projectile caches. Useful after creating or removing a project marker, since the existing projectile-invalidate-cache either also drops the file list cache or prompts for a project depending on context.
  • Warn once per session when projectile-indexing-method' is alien' but the project has a non-empty .projectile' file, so users notice their dirconfig rules are being bypassed. Controlled by the new projectile-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-name and project-buffers methods for the project.el integration, so that code using project.el APIs returns correct results for Projectile-managed projects.
  • Implement the project-ignores method for the project.el integration, translating Projectile's global ignores, ignored file suffixes, and dirconfig (.projectile) ignore entries into the glob format project.el expects. This makes Projectile a more complete project.el backend for tools that rely on the protocol (e.g. project-find-regexp).
  • Add projectile-forget-projects-under to drop all known projects located under a directory (with a prefix argument it recurses into nested projects). Mirrors project.el's project-forget-projects-under.
  • Add projectile-forget-zombie-projects as an alias for projectile-cleanup-known-projects, for discoverability and parity with project.el's project-forget-zombie-projects.
  • projectile-kill-buffers-filter now also accepts a composable list of conditions (buffer-name regexps, predicates, and major-mode/derived-mode/not/and/or forms), modeled on project.el's project-kill-buffer-conditions. The existing kill-all, kill-only-files, and predicate-function values keep working unchanged.
  • #1837: Add eat project terminal commands with keybindings x x and x 4 x.
  • Add ghostel project terminal commands with keybindings x G and x 4 G.
  • Add keybinding A (in the projectile command map) and a menu entry for projectile-add-known-project.
  • #1653: Add projectile-compile-subproject and projectile-test-subproject commands for building/testing individual modules in multi-module projects (e.g. Maven, Gradle). Bound to c m c and c m t.

Bugs fixed

  • #2005: Fix hybrid indexing with fd when the .projectile file has + keep entries. The kept directories were appended to the fd command as bare positional arguments, but fd's grammar is [pattern] [path...] (so the first path was misread as the search pattern) and fd 9+ additionally rejects --strip-cwd-prefix alongside explicit paths (error: ... '--strip-cwd-prefix' cannot be used with '[path]...'). Projectile now passes the directories to fd via --search-path and drops --strip-cwd-prefix in that case. Other tools (git ls-files, find, etc.) still get plain trailing paths.
  • #2042: Asynchronous indexing now runs its command under /bin/sh rather than the user's interactive shell-file-name. The async runner wraps the command as { ...; } 2>file, which is POSIX-sh syntax and broke for users whose shell is csh/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-dispatch menu now run in the project that was just selected, instead of the current buffer's project. The menu is a transient, 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 via C-u s-p p or by setting projectile-switch-project-action to projectile-dispatch (or any other transient).
  • #2037: projectile-consult.el now byte-compiles cleanly when consult isn't installed. Because the module ships inside the Projectile MELPA package, build systems (package.el, nixpkgs) try to compile it without consult present, which previously errored on its top-level (require 'consult). consult is 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 aborts projectile-find-file when the command still produced a file listing. External listers like fd routinely exit non-zero on benign conditions (e.g. an unreadable directory hit mid-traversal); that output is now used. A user-error is 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 (or projectile-ignored-project-function) are now excluded from projectile-relevant-known-projects, so they no longer show up in projectile-switch-project even 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-files function now receives the project root as its argument when detecting the current project's type. Previously it was passed nil, so such a function could never match the current project.
  • #1829: projectile-project-root no longer errors with (wrong-type-argument stringp nil) when default-directory is nil (which can happen in some non-file buffers); it returns nil instead.
  • #1946: projectile-ripgrep now builds its ignore exclusions as --glob=!PATTERN instead of --glob '!PATTERN'. The surrounding single quotes were only stripped by POSIX shells, so on Windows cmd they became part of the pattern and the exclusions silently failed.
  • #2008: A project type with an empty marker-files list no longer matches every project. projectile-verify-files is vacuously true for an empty list, so a type whose markers were cleared (e.g. via projectile-update-project-type ... :marker-files nil) would be detected everywhere instead of nowhere. projectile-detect-project-type now treats an empty marker set as a non-match.
  • projectile-discard-root-cache' and projectile-invalidate-cache' now also clear projectile-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 to projectile-file-exists-remote-cache-expire' seconds even after the user explicitly invalidated the root cache.
  • projectile-find-file-hook-function no 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 used file-readable-p, which is a remote round-trip per project; with projectile-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 with projectile-remove-known-project'.
  • Skip file-truename for remote paths in projectile-ignored-project-p and projectile-ignored-projects. The known-projects tracker calls projectile-ignored-project-p from 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-p now skips the file-truename call for buffers visiting remote (TRAMP) files. Each truename was a remote round-trip and projectile-project-buffers' iterates the full buffer-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-p probes in projectile-project-vcs with a single directory-files listing, and cache the per-project result in projectile-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-root round-trips on TRAMP. The cache-validity check used raw file-exists-p on the cached project root, which is a fresh remote stat on every call; switched to projectile-file-exists-p so 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/fdfind on the remote host for TRAMP projects instead of reusing the locally-detected projectile-fd-executable. The locally-resolved value is meaningless on the remote, so when fd was 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 new projectile-fd-executable-for returns the per-directory executable, with a per-host cache so the remote executable-find lookup runs at most once per host per session.
  • #1898, #1932: Surface non-zero exit codes from the indexing command in projectile-files-via-ext-command instead of silently returning an empty file list. The most common cause is fd/git being installed locally but not on the remote host of a TRAMP project — previously this manifested as an unexplained empty completion list (or stringp, nil crashes downstream); it now signals a user-error pointing at the *projectile-files-errors* buffer for the captured stderr.
  • #1211: Fix file-local projectile-project-root overrides being ignored after the first buffer in a directory was visited. The cache used (default-directory) as part of the key but projectile-root-local reads a buffer-local variable, so two buffers in the same directory with different overrides got the first buffer's answer. Results from projectile-root-local are no longer cached.
  • #1836: Memoize per-function nil results in the project root cache. Previously, when an early entry in projectile-project-root-functions returned nil, the nil was indistinguishable from a missing cache entry, so the function was re-run on every projectile-project-root call. With many entries this could cost several full directory walks per call. A 'none sentinel 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-command executing empty string as shell command for non-git VCS sub-projects.
  • Fix projectile-select-files crashing on filenames with regexp metacharacters by using string-search instead of string-match.
  • Fix projectile-find-references using internal xref--show-xrefs API whose signature changed across Emacs versions.
  • Fix projectile-determine-find-tag-fn falling back to find-tag which was removed in Emacs 29; now falls back to xref-find-definitions.
  • Fix projectile-files-to-ensure expanding wildcards relative to the current buffer instead of the project root.
  • Fix projectile-ignored-project-p failing to match abbreviated paths against truename-resolved ignored projects list.
  • Fix projectile-edit-dir-locals saving partial .dir-locals.el content when the user aborts skeleton insertion with C-g.
  • Fix projectile--other-extension-files sort comparator ignoring its second argument, producing undefined ordering; replaced with a stable partition.
  • Fix projectile-toggle-project-read-only operating on the wrong buffer after add-dir-local-variable by wrapping in save-selected-window.
  • Fix projectile-cache-current-file calling projectile-project-root twice instead of reusing the already-resolved value.
  • Fix projectile-cache-current-file queueing 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-cache not recording a cache time, which combined with projectile-files-cache-expire made 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 seed projectile-projects-cache-time.
  • Fix projectile-load-project-cache storing nil in cache on corrupt/empty cache files, preventing future reload attempts.
  • Fix projectile-purge-dir-from-cache only 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 of projectile-purge-file-from-cache.
  • projectile-invalidate-cache now deletes the persistent cache file instead of overwriting it with a serialized nil.
  • Fix projectile--cmake-command-presets using mapcar instead of mapcan, producing nested lists for included presets.
  • Fix projectile--eat ignoring the new-process argument when generating buffer names.
  • Fix projectile-check-vcs-status hanging indefinitely by adding a 30-second timeout to its busy-wait loop.
  • Fix projectile-sort-by-modification-time and projectile-sort-by-access-time crashing on deleted files (nil file-attributes).
  • Fix projectile-recentf-files failing to match files when the project root contains symlinks.
  • Fix projectile--run-project-cmd passing nil to compile when no command is configured and compilation-read-command is nil — now signals a clear user-error.
  • Fix projectile--merge-related-files-fns using destructive nconc which could corrupt shared data and silently drop values when the existing list was nil.
  • Fix projectile-configure-command format call passing an unused compile-dir argument.
  • Fix projectile-update-project-type resetting the project type cache with wrong hash table :test (used default eql instead of equal for string keys).
  • Fix projectile-find-file-hook-function running projectile-maybe-limit-project-file-buffers on TRAMP buffers, causing potential hangs on slow connections.
  • Use expand-file-name instead of file-truename in projectile-compilation-dir to avoid unnecessary symlink resolution (and TRAMP network round-trips).
  • Use non-destructive append instead of nconc in projectile-get-all-sub-projects to avoid mutating caller data.
  • Add safe-local-variable predicates for project settings variables (projectile-project-test-suffix, projectile-project-compilation-cmd, etc.) so they can be set in .dir-locals.el without prompting.
  • #1962: Fix projectile-get-other-files crashing when a candidate other-file lives in the project root directory.
  • #1816: Fix projectile-expand-file-name-wildcard failing when a parent directory is not readable (e.g. iCloud Drive, Termux).
  • #1841: Preserve user's compilation-buffer-name-function when projectile-per-project-compilation-buffer is nil.
  • #1823: Update the mode-line via window-configuration-change-hook so 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 using projectile-acquire-root instead of projectile-project-root in projectile--run-project-cmd.
  • #1456: Fix projectile-replace-regexp stopping when encountering a missing file by filtering nonexistent files from the replacement file list.
  • #1687: Fix projectile-grep-finished-hook not running on subsequent grep calls due to buffer rename collision.
  • #1939: Handle corrupted projectile-known-projects-file gracefully instead of crashing with (wrong-type-argument listp ...).
  • #1748: Fix projectile-replace falling back to the legacy Emacs 25/26 code path on Emacs 27+ because fileloop was not loaded.
  • #1741: Fix projectile-replace treating the search string as a regexp instead of a literal string on Emacs 27+.
  • #1729: Fix projectile-root-top-down to actually return the topmost matching project root instead of the bottommost.
  • #1596: projectile-find-dir now includes intermediate directories that contain only subdirectories (e.g. src/ when it only has src/ComponentA/, src/ComponentB/).
  • #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: Fix projectile-files-with-string failing on special characters when using grep or git-grep by adding the -F (fixed-string) flag.
  • #1897: Filter deleted-but-unstaged files from git ls-files output in alien/hybrid indexing (when fd is 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 workspace directory no longer matches the WORKSPACE Bazel marker on case-insensitive filesystems).
  • #1749: Strip ./ prefix from fd output in projectile-files-via-ext-command, fixing compatibility with older fd versions that don't support --strip-cwd-prefix.
  • Fix projectile-ripgrep failing on zsh when ignored file patterns contain glob characters, by quoting --glob arguments.
  • Fix CMake version parsing failing when cmake --version output 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 jj output format.
  • Fix projectile-purge-file-from-cache serializing the stale file list to disk instead of the updated one.
  • Fix misplaced paren in projectile-project-buffers-other-buffer causing switch-to-buffer arguments to be ignored.
  • Fix projectile-default-generic-command silently dropping lambda/closure commands (only symbol commands worked).
  • Fix dired-before-readin-hook being added as buffer-local instead of global in projectile-mode, so it now correctly fires in all dired buffers.
  • Fix projectile-find-dir-hook not being cleaned up when disabling projectile-mode.
  • Use display-warning instead of message when cache serialization fails, making the failure visible in the *Warnings* buffer.