From 53aa780b63b0d1967e9d101c1972997b8cff39bc Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 29 Jul 2026 16:16:58 +0200 Subject: [PATCH] Resolve the state files with locate-user-emacs-file The known projects file, the frecency store and the session directory were each expanded against user-emacs-directory by hand. Since Emacs 27 a configuration can live in ~/.config/emacs, and locate-user-emacs-file is what knows that - which is why bookmark, recentf, savehist and project.el all go through it. Projectile's three didn't, so they landed in ~/.emacs.d for people whose Emacs state is elsewhere. Nothing moves for a configuration in the usual place, which is what the last spec pins. The :package-version tags are bumped because the default does change for everyone else. The per-project file cache is deliberately untouched: it lives in the project on purpose, and XDG has nothing to offer it. --- CHANGELOG.md | 1 + projectile.el | 20 ++++++++++++-------- test/projectile-core-test.el | 27 +++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a216cee46..f7e3936a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ ### Bugs fixed +- [#2145](https://github.com/bbatsov/projectile/pull/2145): The known projects file, the frecency store and the session directory are resolved with `locate-user-emacs-file` rather than expanded against `user-emacs-directory` by hand, so they land beside the rest of your Emacs state when your configuration lives in `~/.config/emacs` instead of `~/.emacs.d`. Nothing moves for a configuration in the usual place. - [#2144](https://github.com/bbatsov/projectile/pull/2144): Internal: the two shapes a project type marker can take are decoded in one place now, rather than by each of the four consumers separately. - [#2142](https://github.com/bbatsov/projectile/pull/2142): Project types can declare `:src-extension` and `:test-extension`, for the languages whose tests don't carry the same extension as their sources. The bundled `elixir` type sets them, so toggling from `lib/foo.ex` now offers to create `test/foo_test.exs` - a script ExUnit will actually run - rather than `test/foo_test.ex`. - [#2141](https://github.com/bbatsov/projectile/pull/2141): The messages Projectile emits without being asked are now prefixed with `[Projectile]`, so it's clear where they came from; the ones that answer a command you just invoked stay unprefixed. Five different conventions across the file (a `Projectile:` prefix, a bare `Projectile` one, the project name in brackets, plain passthrough, and no prefix at all) become one rule. diff --git a/projectile.el b/projectile.el index 601d0eb30..bb0f8971a 100644 --- a/projectile.el +++ b/projectile.el @@ -1189,12 +1189,16 @@ Contains a copy of `projectile-known-projects' when it was last synchronized with `projectile-known-projects-file'.") (defcustom projectile-known-projects-file - (expand-file-name "projectile-bookmarks.eld" - user-emacs-directory) - "Name and location of the Projectile's known projects file." + (locate-user-emacs-file "projectile-bookmarks.eld") + "Name and location of the Projectile's known projects file. + +Resolved with `locate-user-emacs-file\\=', so it lands beside the rest of +your Emacs state wherever that is - including `~/.config/emacs\\=' for a +configuration kept there, which a hand-rolled `user-emacs-directory\\=' +path would have missed." :group 'projectile :type 'string - :package-version '(projectile . "0.9.0")) + :package-version '(projectile . "3.4.0")) (defcustom projectile-ignored-projects nil "A list of projects not to be added to `projectile-known-projects'." @@ -2447,11 +2451,11 @@ The history is persisted in `projectile-frecency-file'." :package-version '(projectile . "3.1.0")) (defcustom projectile-frecency-file - (expand-file-name "projectile-frecency.eld" user-emacs-directory) + (locate-user-emacs-file "projectile-frecency.eld") "File where Projectile persists the per-project file visit history." :group 'projectile :type 'file - :package-version '(projectile . "3.1.0")) + :package-version '(projectile . "3.4.0")) (defcustom projectile-frecency-max-files 200 "Maximum number of files tracked per project. @@ -15991,13 +15995,13 @@ component." :package-version '(projectile . "3.2.0")) (defcustom projectile-session-directory - (expand-file-name "projectile-sessions/" user-emacs-directory) + (locate-user-emacs-file "projectile-sessions/") "Directory under which per-project session files are stored. Each project's saved layout and buffers live in a single file here, named after the project (see `projectile-session--file')." :group 'projectile :type 'directory - :package-version '(projectile . "3.2.0")) + :package-version '(projectile . "3.4.0")) (defcustom projectile-session-restore-on-switch t "Whether switching to a project restores its saved session. diff --git a/test/projectile-core-test.el b/test/projectile-core-test.el index 129e12519..d78919d6d 100644 --- a/test/projectile-core-test.el +++ b/test/projectile-core-test.el @@ -605,4 +605,31 @@ (projectile--message "Cleared the project root cache") (expect said :not :to-match "[.!]\\'"))))) +(describe "where Projectile keeps its state" + ;; `locate-user-emacs-file' honours `~/.config/emacs' when the configuration + ;; lives there, which a hand-rolled `user-emacs-directory' path does not. + (it "resolves the known projects file through locate-user-emacs-file" + (expect (eval (car (get 'projectile-known-projects-file 'standard-value)) t) + :to-equal (locate-user-emacs-file "projectile-bookmarks.eld"))) + + (it "resolves the frecency file through locate-user-emacs-file" + (expect (eval (car (get 'projectile-frecency-file 'standard-value)) t) + :to-equal (locate-user-emacs-file "projectile-frecency.eld"))) + + (it "resolves the session directory through locate-user-emacs-file" + (expect (eval (car (get 'projectile-session-directory 'standard-value)) t) + :to-equal (locate-user-emacs-file "projectile-sessions/"))) + + (it "keeps the session directory a directory name" + ;; It is expanded against, so losing the trailing slash would put the + ;; session files beside it rather than inside it. + (let ((dir (eval (car (get 'projectile-session-directory 'standard-value)) t))) + (expect (directory-name-p dir) :to-be-truthy))) + + (it "still lands in user-emacs-directory for a stock configuration" + ;; The common case must be unchanged - this is a fix for the XDG one. + (let ((user-emacs-directory "~/.emacs.d/")) + (expect (locate-user-emacs-file "projectile-bookmarks.eld") + :to-equal "~/.emacs.d/projectile-bookmarks.eld")))) + ;;; projectile-core-test.el ends here