aboutsummaryrefslogtreecommitdiff
path: root/projectile.el
AgeCommit message (Collapse)Author
2026-04-27Coalesce idle-timer flushes in projectile-cache-current-fileHEADmasterBozhidar Batsov
Each call to `projectile-cache-current-file' under persistent caching scheduled a fresh 30-second idle timer that closed over a snapshot of the file list at scheduling time. Opening N files in a session queued N timers; once Emacs went idle they all fired and serialized the cache N times, with the earlier ones writing stale (shorter) lists. Maintain a per-project pending timer in `projectile--pending-cache-flush-timers' instead, cancelling and rescheduling on each new file. The fired callback re-reads the current in-memory cache, so the disk write reflects the final state rather than whichever snapshot the last timer captured.
2026-04-27Delete cache file in projectile-invalidate-cacheBozhidar Batsov
Previously the persistent invalidate path called `(projectile-serialize nil ...)', leaving a four-byte file containing the literal "nil" on disk. Resolving the long-standing TODO: just delete the file when invalidating, so the on-disk state matches the intent of the command (and so an orphaned cache file isn't left behind on uninstall).
2026-04-27Persist disk cache in projectile-purge-dir-from-cacheBozhidar Batsov
`projectile-purge-file-from-cache' writes the updated file list to disk under persistent caching; the directory variant only mutated the in-memory hash, so the purged subtree would reappear on the next Emacs session. Mirror the persistence step here.
2026-04-27Seed cache time when loading the project file cache from diskBozhidar Batsov
Without this, `projectile-files-cache-expire' combined with persistent caching ended up re-reading the cache file from disk on every call to `projectile-project-files' and never reindexing: the TTL check at the top of `projectile-project-files' evicts the in-memory entry whenever its cache time is missing, and `projectile-load-project-cache' was populating only `projectile-projects-cache' on disk loads. Use the cache file's mtime as the recorded time, so the TTL check sees a real age and reindexing happens when (and only when) the data is actually stale.
2026-04-26Skip git submodule scan when there is no .gitmodulesBozhidar Batsov
`projectile-get-immediate-sub-projects' was unconditionally shelling out to `git submodule --quiet foreach ...' on every indexing call for git projects, even when the project had no submodules at all. For monorepos that re-index the project root often this is pure overhead. Use `locate-dominating-file' to look for `.gitmodules' along the parent chain (PATH may be inside a git repo without being its toplevel) and skip the shell-out when none is found. Also tighten `projectile-discover-projects-in-directory' to filter `.' / `..' via `directory-files-no-dot-files-regexp' instead of a post-filter `member' check, matching the indexing walker's style.
2026-04-26Speed up native + hybrid indexing's ignore filteringBozhidar Batsov
Hash-set the ignored-files / ignored-directories / globally-ignored- directory-names lists once per indexing call, instead of repeating linear `member' scans on every file. On a project with N files and M ignore entries this drops the inner work from O(N*M) to roughly O(N). In the native walker (`projectile-index-directory') three additional changes: * Glob patterns for `.projectile' ignore/ensure are pre-expanded once per directory level, instead of once per (file, pattern) pair via `file-expand-wildcards' inside `projectile-check-pattern-p'. * Discovered files accumulate into a single mutable cell threaded through the recursion, so we don't pay for an `apply append' at each level (which copied every file once per level it bubbled through). * `default-directory' is rebound for glob expansion, but only after the directory listing is captured - so callers can still pass a relative `directory' argument. In the hybrid post-processor (`projectile-remove-ignored'), pre-split ignored-dirs into prefix-match and any-segment groups and hash the segment names. The per-file segment loop becomes a hash lookup; the basename match against ignored-files is now also a hash lookup. The public functions (`projectile-ignored-file-p', `projectile- ignored-directory-p', `projectile-ignored-rel-p', `projectile-check-pattern-p') keep their existing signatures and behaviour for external callers.
2026-04-26Batch hybrid indexing across `+' keep dirconfig entriesBozhidar Batsov
When a project's `.projectile' declares multiple `+' keep entries, hybrid indexing used to walk each subdirectory individually, shelling out to the external indexing command once per entry. The TODO in `projectile-project-files' had been there for a while. Push the kept paths into the external command as positional pathspecs and run `projectile-adjust-files' once over the combined result. `git ls-files', `fd', `find', `hg locate', etc. all accept additional path arguments at the end of the command line. For Git submodules, `projectile-get-sub-projects-files' is queried once and the result is filtered to only those falling under one of the kept subdirectories. `projectile-files-via-ext-command' grows an optional `pathspecs' arg (shell-quoted before being appended); `projectile-dir-files-alien' grows a matching optional `subdirs' arg that threads through.
2026-04-26Avoid recomputing VCS in projectile-dir-files-alienBozhidar Batsov
The dispatcher in `projectile-dir-files` already resolves the VCS for the hybrid path. Thread it through to `projectile-dir-files-alien` via a new optional argument so we don't pay for a second `projectile-project-vcs` call on every indexing run. Single-argument callers (and the spies in the test suite) keep working unchanged. Also drop the post-hoc `(member local-f '("." ".."))` filter in `projectile-index-directory` in favour of `directory-files`' `directory-files-no-dot-files-regexp`, which handles the same case at the C level.
2026-04-26Share walk skeleton between locate-dominating-file helpersBozhidar Batsov
projectile-locate-dominating-file (bottommost match) and projectile-locate-dominating-file-top-down (topmost match) only differed in two lines: the bottom-up version exits the walk on first match, the top-down version keeps walking and overwrites root at each level. Extract a shared projectile--locate-dominating-file with a first-match-only flag. The two public functions become one-liners that delegate. Loop semantics preserved - 277 tests still pass.
2026-04-26Add projectile-discard-root-cache command (#1936)Bozhidar Batsov
The existing projectile-invalidate-cache always tries to also clear the per-project files cache, which means it either prompts for a project (with prefix arg) or only operates when you're already in a project. Users who just created a marker file in a directory that Projectile previously considered rootless ended up picking an unrelated project from the prompt just to get the root cache flushed. projectile-discard-root-cache is a focused alternative that empties projectile-project-root-cache only. Wired into the menu next to the existing 'Invalidate cache' entry; deliberately not bound on the prefix map since the keymap is already dense.
2026-04-26Document the project root cache and marker semanticsBozhidar Batsov
- New 'Project root cache' subsection in projects.adoc covering invalidation, when negative results bite (the recurring 'I created .projectile and Projectile still doesn't see it' confusion from #1936), and how to reset programmatically. - Note in 'Customizing Project Detection' that projectile-root-top-down only matches regular files while projectile-root-bottom-up matches both - this difference is what forces VCS markers like .git onto the bottom-up list. - Expand the file-local override section with a concrete example and call out that overrides bypass the cache. - Rewrite projectile-project-root's docstring to mention caching, the tramp-archive unwrap, and the disconnected-remote behaviour that callers used to have to read the source to discover.
2026-04-26Memoize per-function nil results in the root cache (#1836)Bozhidar Batsov
Previously a root function returning nil stored nil in the cache, which is indistinguishable from a missing entry, so on the next call the function was re-run. When a project's root is found by the third function in projectile-project-root-functions, the first two re-walked the tree on every call - and projectile-project-root is hot in mode-line / company / spaceline updates. Store the symbol 'none for unsuccessful entries and treat it as a cache hit on lookup. The overall-failure marker already used 'none in a separate slot, so this just extends the same convention to the per-function entries.
2026-04-26Skip cache for projectile-root-local (#1211)Bozhidar Batsov
projectile-root-local reads the buffer-local projectile-project-root variable rather than inspecting its DIR argument, so caching its result by (FUNC . DIR) was wrong: two buffers visiting the same directory but with different file-local overrides would share the first buffer's cached answer. Skip the cache for this function specifically - it's a single variable read, no FS work to amortize. Adds a regression test that visits two buffers with different file-local roots from the same default-directory and confirms each sees its own override.
2026-04-26Use cons cells as projectile-project-root-cache keysBozhidar Batsov
Per-function entries are now keyed on (FUNC . DIR), and the overall failure marker on ('none . DIR), instead of formatted strings. Same equal semantics, no per-call format allocation, and lambda functions in projectile-project-root-functions hash by identity instead of by their printed repr.
2026-04-25Small dirconfig cleanupsdirconfig-polishBozhidar Batsov
- projectile-get-project-directories: replace the (or keep '("")) trick with an explicit (if keep ... (list project-dir)). The behavior is unchanged but the empty-keep case no longer reads as a string-concat with the empty string. - projectile-dirconfig-file: expand the docstring to spell out the dual marker/config role — empty file is enough to mark a project, non-empty content drives parsing.
2026-04-25Include dirconfig path in the parse-cache hit checkBozhidar Batsov
The cache value used to be (MTIME . PARSED-RESULT) keyed on the project root. If the user changed `projectile-dirconfig-file' to point at a different file mid-session, the old entry kept getting returned because the key didn't depend on the path. Store the path alongside the mtime in the cached value and compare both before considering a hit. The hash key remains the project root so `projectile-invalidate-cache' continues to work as-is.
2026-04-25Fire the + glob keep warning once per projectBozhidar Batsov
The other two dirconfig warnings (alien-mode bypass and prefix-less entries) are gated by per-project hash sets so they fire at most once per Emacs session. The glob-keep warning was the odd one out — it lived inside the uncached parser and re-fired on every cache miss, and it emitted one display-warning per offending entry rather than a single consolidated message. Move it into the cached wrapper alongside the others, gate it on projectile--glob-keep-warned-projects, and roll multiple offending entries into one warning that lists them.
2026-04-25Soft-deprecate prefix-less dirconfig entriesdirconfig-refactorBozhidar Batsov
The implicit "any unprefixed line is an ignore pattern" rule is the last source of subtle parser surprises — it's the reason why a single leading space silently changes a +-keep into a literal ignore pattern, and it makes typo'd comments slip through as ignores. Mark these lines as :legacy-ignore in the classifier, record them in a new prefixless-ignore slot on the dirconfig struct, and emit a one-time warning per project listing the offending entries. The behavior is unchanged — the lines still go into the ignore list — but users now get a nudge to write them as -entry. The warning can be silenced via projectile-warn-on-prefixless-dirconfig-lines.
2026-04-25Split the dirconfig parser into a pure line classifierBozhidar Batsov
The old parser walked a temp buffer with point and pcase'd on char-after, mixing IO, prefix dispatch, and bucket bookkeeping into one function. Pull the dispatch out into projectile--dirconfig-classify-line, which takes a string and returns a (BUCKET . VALUE) tag. The pure function is unit-testable without buffer plumbing, the IO wrapper shrinks to a one-shot read + dispatch, and the awkward (pcase ((pred (lambda ...)) ...)) for the comment-prefix check becomes a straightforward cond. No behavior change.
2026-04-25Return a projectile-dirconfig struct from the parserBozhidar Batsov
Replace the positional (KEEP IGNORE ENSURE) triple with a cl-defstruct. Every internal call site used car/cadr/caddr to pull out a slot, which is unreadable and error-prone — slot accessors make the intent explicit and let cl-defstruct grow a fourth field later without touching every consumer. Existing callers that compared against the raw triple (a couple of internal helper tests) are updated to construct the struct with make-projectile-dirconfig.
2026-04-25Warn when a + keep entry contains glob metacharactersdirconfig-improvementsBozhidar Batsov
The parser silently turns every keep entry into a directory via file-name-as-directory, which means a user-typed +*.json or +/foo.txt becomes "*.json/" or "foo.txt/" and quietly never matches anything. Spot the obvious misuses (anything containing *, ?, or [) at parse time and emit a warning so the user can correct the file or move the pattern to an ignore/ensure rule.
2026-04-25Warn when alien indexing bypasses a populated .projectileBozhidar Batsov
Under alien indexing the dirconfig file is silently ignored, which is the most common confusion in the issue tracker (#1322, #1075, #1534, #1941). Show a one-shot display-warning the first time we index a project where alien mode meets a non-empty .projectile. The new projectile-warn-when-dirconfig-is-ignored option lets users who already understand the trade-off silence the warning.
2026-04-25Document the dirconfig format more preciselyBozhidar Batsov
The parser docstring only described + and - prefixes even though the function returns a 3-tuple including the ! ensure bucket. Fill that in, and pull the path-vs-glob distinction out of the prose in projects.adoc into a subsection of its own — that ambiguity is at the root of recurring confusion (#740, #1109, #680, #1941).
2026-04-25Skip leading whitespace in dirconfig prefix dispatchBozhidar Batsov
A user accidentally typing " -path/" or " # comment" in .projectile would have the line silently routed to the ignore bucket with the prefix character left intact, because the parser dispatched on the first character of the line without trimming. Skip leading spaces and tabs before the pcase so the +/-/! and comment-prefix markers are matched regardless of indentation. Reported in #1508.
2026-03-10Disambiguate process buffer names for same-named projectsBozhidar Batsov
Fixes #1815 Fixes #1715 When two projects share the same name (e.g. both named "src"), projectile-generate-process-name now detects the collision and falls back to using the abbreviated project path in the buffer name instead of just the project name.
2026-03-10Check globally-ignored-file-suffixes in native indexingBozhidar Batsov
Fixes #1959 projectile-ignored-file-p now checks projectile-globally-ignored-file-suffixes in addition to projectile-globally-ignored-files and file patterns. Previously the suffix check only happened in projectile-remove-ignored which is used by hybrid indexing but not native indexing.
2026-03-10Allow projectile-dired commands to prompt for a projectBozhidar Batsov
Closes #1684 With a prefix argument, projectile-dired, projectile-dired-other-window, and projectile-dired-other-frame now prompt for a known project to open in dired, rather than always using the current project.
2026-03-10Filter projectile-replace results through project ignore rulesBozhidar Batsov
Closes #1227 projectile-replace now intersects the files found by rg/ag/grep with the project's file list from projectile-dir-files, ensuring that files ignored via .projectile or other ignore rules are excluded from replacement. Previously the external search tool's results were used directly, which could include backup files and other ignored entries.
2026-03-10Add projectile-find-file-all to find files ignoring VCS rulesBozhidar Batsov
Closes #1001 Add a new command that lists all files under the project root using a generic file listing command (fd or find), bypassing .gitignore, .projectile, and other ignore mechanisms. This is useful when you need to find files that are normally excluded from the project.
2026-03-05Fix compilation-find-file advice for dirs without direct filesBozhidar Batsov
Fixes #1923 When compilation output references a file via a relative path, the advice now also checks if the file exists relative to the project root and adds its parent directory to the search path. This handles the edge case where a directory only contains subdirectories (no files directly) and therefore was not included in the file-derived directory list from projectile-current-project-dirs.
2026-03-04Update CHANGELOG with round 4 codebase fixesBozhidar Batsov
2026-03-04Wrap add-dir-local-variable in save-selected-window in toggle-read-onlyBozhidar Batsov
add-dir-local-variable switches to the .dir-locals.el buffer. Without save-selected-window, kill-buffer and the subsequent buffer-file-name check operated on unpredictable buffers. This matches the pattern already used in projectile-add-dir-local-variable.
2026-03-04Replace broken sort with partition in projectile--other-extension-filesBozhidar Batsov
The seq-sort comparator ignored its second argument, producing undefined ordering. The intent was just to prioritize files from sibling directories. Replace with a clear two-bucket partition: sibling-dir files first, then the rest.
2026-03-04Don't save .dir-locals.el on user abort in projectile-edit-dir-localsBozhidar Batsov
unwind-protect ran save-buffer even when the user aborted skeleton insertion with C-g, creating a .dir-locals.el with empty/partial content. Run save-buffer sequentially after the skeleton instead, so aborting leaves no file on disk.
2026-03-04Normalize project root to truename in projectile-ignored-project-pBozhidar Batsov
projectile-ignored-projects returns truename-resolved paths, but callers pass abbreviated or unnormalized paths. The member check would fail to match (e.g., ~/work/ vs /Users/bob/work/). Resolve project-root to truename before comparing.
2026-03-04Fix projectile-files-to-ensure expanding wildcards in wrong directoryBozhidar Batsov
file-expand-wildcards uses default-directory for relative patterns, which was whatever the current buffer had rather than the project root. Bind default-directory to the project root and return relative paths so projectile-expand-paths resolves them correctly downstream.
2026-03-04Fall back to xref-find-definitions instead of removed find-tagBozhidar Batsov
find-tag was removed in Emacs 29. When the configured tags backend (ggtags or etags-select) is unavailable, fall back to xref-find-definitions instead, which is available since Emacs 25.1.
2026-03-04Use public xref-show-xrefs API in projectile-find-referencesBozhidar Batsov
Replace private xref--show-xrefs with public xref-show-xrefs. The private function's signature changed across Emacs versions. Keep xref-references-in-directory to scope the search to Projectile's project root.
2026-03-04Use string-search instead of string-match in projectile-select-filesBozhidar Batsov
The filename text at point (from thing-at-point or region) was used directly as a regexp pattern in string-match. Characters like [ ] ( ) would cause invalid-regexp errors or incorrect matches. Use string-search for literal substring matching instead.
2026-03-04Don't execute empty string as shell command for non-git sub-projectsBozhidar Batsov
projectile-get-sub-projects-command returned "" for non-git VCSes, which passed the stringp guard in projectile-files-via-ext-command and spawned a useless shell process. Return nil instead, and also make the guard defensive against empty strings to match the docstring's contract.
2026-02-28Use append instead of nconc in projectile--merge-related-files-fnsBozhidar Batsov
nconc destructively modifies the existing plist value, which can corrupt shared data from related-files-fn return values. It also silently drops values when the existing list is nil (nconc of nil doesn't update the plist entry in place). Using append with explicit plist-put fixes both issues.
2026-02-28Error early on nil command in projectile--run-project-cmdBozhidar Batsov
When no command is configured for a project type and compilation-read-command is nil (skipping the prompt), the nil command would reach (compile nil) and signal a cryptic wrong-type-argument error. Signal a clear user-error instead.
2026-02-28Use file-truename in projectile-recentf-files for symlink matchingBozhidar Batsov
recentf stores canonical paths (with symlinks resolved), so the project root must also be truename-resolved for string-prefix-p matching to work when the project path contains symlinks.
2026-02-28Handle deleted files in sort-by-modification/access-timeBozhidar Batsov
file-attributes returns nil for deleted files, causing file-attribute-modification-time to error. Use 0 as the fallback timestamp so deleted files sort to the end instead of crashing.
2026-02-28Share truename cache across buffers in projectile-open-projectsBozhidar Batsov
Like projectile-project-buffers, pass a shared truename-cache hash table to projectile-project-buffer-p to avoid redundant file-truename calls when iterating over the buffer list.
2026-02-28Remove unused compile-dir arg from configure-command format callBozhidar Batsov
The format call passed both project-root and compile-dir, but no project type's configure command uses two %s placeholders (meson uses one for the project root). The extra argument was silently ignored.
2026-02-28Remove unnecessary temp buffer in projectile--cache-project-commands-pBozhidar Batsov
The function created a temp buffer and ran hack-dir-local-variables on every compile/test/run invocation just to read a variable that dir-locals already set buffer-locally in file buffers. Simply read the variable directly.
2026-02-28Add 30s timeout to projectile-check-vcs-status busy-waitBozhidar Batsov
The unbounded (while (vc-dir-busy) ...) loop could freeze Emacs indefinitely if vc-dir hangs (e.g., broken remote repo). This is especially dangerous when called for every known project via projectile-check-vcs-status-of-known-projects.
2026-02-28Pass new-process to buffer name generation in projectile--eatBozhidar Batsov
The new-process argument was ignored when generating the eat buffer name (hardcoded nil), so requesting a new process via prefix arg would always reuse the same buffer name. This matches the behavior already used in projectile--vterm.
2026-02-28Use mapcan instead of mapcar in cmake-command-presetsBozhidar Batsov
mapcar wraps each recursive result in a list, producing nested lists that only work because flatten-tree compensates downstream. mapcan properly concatenates the results into a flat list.