Projects
Supported Project Types
One of the main goals of Projectile is to operate on a wide range of project types without the need for any configuration. To achieve this it contains a lot of project detection logic and project type specific logic.
Broadly speaking, Projectile identifies projects like this:
-
Directories that contain the special
.projectilefile -
Directories under version control (e.g. a Git repo)
-
Directories that contain some project description file (e.g. a
Gemfilefor Ruby projects orpom.xmlfor Java maven-based projects)
While Projectile aims to recognize most project types out-of-the-box, it’s also extremely flexible configuration-wise, and you can easily alter the project detection logic.
If you’d like to override the default project detection functions you should
check out projectile-project-root-functions. We’ll discuss how to tweak in more
details later in the documentation.
|
Version Control Systems
Projectile considers most version-controlled repos to be a project. Out of the box Projectile supports:
-
Git
-
Mercurial
-
Bazaar
-
Subversion
-
CVS
-
Fossil
-
Darcs
-
Sapling
-
Jujutsu
File markers
Projectile considers many files to denote the root of a project. Usually those files are the configuration files of various build tools. Out of the box the following are supported:
| Language/Family | File | Project Type |
|---|---|---|
Universal |
|
xmake project |
Universal |
|
Scons project file |
Universal |
|
project file |
Universal |
|
Nix project file |
Universal |
|
Nix flake project file |
Universal |
|
Bazel workspace file |
Universal |
|
Debian package dpkg control file |
Make & CMake |
|
Make |
Make & CMake |
|
GNU Make |
Make & CMake |
|
CMake |
Go-task/Task |
|
Go-task/Task project file |
PHP |
|
PHP project file |
Erlang & Elixir |
|
Rebar project file |
Erlang & Elixir |
|
Elixir mix project file |
JavaScript |
|
Grunt project file |
Angular |
|
Angular project file |
JavaScript |
|
Javascript Gulp file |
JavaScript |
|
npm, pnpm and yarn project file |
Python |
|
Django project file |
Python |
|
Python requirements file |
Python |
|
Setuptools file |
Python |
|
Python Tox file |
Python |
|
Python Pip file |
Python |
|
Python Poetry project file |
Python |
|
Python project file |
Java & friends |
|
Maven project file |
Java & friends |
|
Gradle project file |
Java & friends |
|
Gradle project file |
Java & friends |
|
Gradle wrapper script |
Java & friends |
|
Gradle project file |
Scala |
|
SBT project file |
Scala |
|
Mill project file |
Scala |
|
Bloop project file |
Ensime |
|
Ensime configuration file |
Clojure |
|
Leiningen project file |
Clojure |
|
Boot-clj project file |
Clojure |
|
Clojure CLI project file |
Ruby |
|
Bundler file |
Crystal |
|
Crystal project file |
Emacs |
|
Emacs cask file |
Emacs |
|
Emacs Eask file |
Emacs |
|
Emacs LISP project file |
R |
|
R package description file |
Haskell |
|
Haskell’s stack tool based project |
Rust |
|
Cargo project file |
Racket |
|
Racket package description file |
Dart |
|
Dart project file |
Elm |
|
Elm project file |
Julia |
|
Julia project file |
OCaml |
|
OCaml Dune project file |
Universal |
|
GNU Global tags |
Universal |
|
etags/ctags are usually in the root of project |
Universal |
|
autoconf new style |
Universal |
|
autoconf old style |
C |
|
cscope |
Composer |
|
Composer project file |
Zig |
|
Zig project file |
Swift |
|
Swift package file |
There’s also Projectile’s own .projectile which serves both as a project marker
and a configuration file. We’ll talk more about later in this section.
Adding Custom Project Types
If a project you are working on is recognized incorrectly or you want to add your own type of projects you can add following to your Emacs initialization code
(projectile-register-project-type 'npm '("package.json")
:project-file "package.json"
:compile "npm install"
:test "npm test"
:run "npm start"
:test-suffix ".spec")
What this does is:
-
add your own type of project, in this case
npmpackage. -
add a list of files and/or folders in a root of the project that helps to identify the type, in this case it is only
package.json. When you list several markers, all of them must be present for the type to match (logical AND). This can also be a function which takes a project root as argument and returns non-nil when that directory has the correct project structure for the type - use that if you instead want to match when any one of several files is present. -
add project-file, which is typically the primary project configuration file. In this case that’s
package.json. When omitted it defaults to the first marker file (so the explicit:project-fileabove is optional here); supply it explicitly when the marker is a function or when the primary file differs from the first marker. The value can contain wildcards and/or be a list containing multiple project files to look for. Pass the symbolnonefor a type that should be detected but never anchor a project root (e.g. its marker also shows up outside real projects). -
add compile-command, in this case it is
npm install. -
add test-command, in this case it is
npm test. -
add run-command, in this case it is
npm start. -
add test files suffix for toggling between implementation/test files, in this case it is
.spec, so the implementation/test file pair could beservice.js/service.spec.jsfor example.
Let’s see a couple of more complex examples.
;; .NET C# or F# projects
(projectile-register-project-type 'dotnet #'projectile-dotnet-project-p
:project-file '("?*.csproj" "?*.fsproj")
:compile "dotnet build"
:run "dotnet run"
:test "dotnet test")
This example uses projectile-dotnet-project-p to validate the project’s structure. Since C# and F# project files have names containing the name of the project, it uses a list of wildcards to specify the different valid project-file name patterns.
;; Ruby + RSpec
(projectile-register-project-type 'ruby-rspec '("Gemfile" "lib" "spec")
:project-file "Gemfile"
:compile "bundle exec rake"
:src-dir "lib/"
:test "bundle exec rspec"
:test-dir "spec/"
:test-suffix "_spec")
;; Ruby + Minitest
(projectile-register-project-type 'ruby-test '("Gemfile" "lib" "test")
:project-file "Gemfile"
:compile "bundle exec rake"
:src-dir "lib/"
:test "bundle exec rake test"
:test-suffix "_test")
;; Rails + Minitest
(projectile-register-project-type 'rails-test '("Gemfile" "app" "lib" "db" "config" "test")
:project-file "Gemfile"
:compile "bundle exec rails server"
:src-dir "lib/"
:test "bundle exec rake test"
:test-suffix "_test")
;; Rails + RSpec
(projectile-register-project-type 'rails-rspec '("Gemfile" "app" "lib" "db" "config" "spec")
:project-file "Gemfile"
:compile "bundle exec rails server"
:src-dir "lib/"
:test "bundle exec rspec"
:test-dir "spec/"
:test-suffix "_spec")
All those projects are using Gemfile (bundler's project file), but they have different directory structures.
Below is a listing of all the available options for projectile-register-project-type:
| Option | Documentation |
|---|---|
:project-file |
A file, relative to the project root, typically the main project file (e.g. |
:compilation-dir |
A path, relative to the project root, from where to run the tests and compilation commands. |
:compile |
A command to compile the project. |
:configure |
A command to configure the project. |
:install |
A function to install the project. |
:package |
A function to package the project. |
:run |
A command to run the project. |
:src-dir |
A path, relative to the project root, where the source code lives. A function may also be specified which takes one parameter - the directory of a test file, and it should return the directory in which the implementation file should reside. This option is only used for implementation/test toggling. |
:test |
A command to test the project. |
:test-dir |
A path, relative to the project root, where the test code lives. A function may also be specified which takes one parameter - the directory of a file, and it should return the directory in which the test file should reside. This option is only used for implementation/test toggling. |
:test-prefix |
A prefix to generate test files names. |
:test-suffix |
A suffix to generate test files names. |
:related-files-fn |
A function to specify test/impl/other files in a more flexible way. |
Returning Projectile Commands from a function
You can also pass a symbolic reference to a function into your project type definition if you wish to define the compile command dynamically:
(defun my/compile-command ()
"Returns a String representing the compile command to run for the given context"
(cond
((and (eq major-mode 'java-mode)
(not (string-match-p (regexp-quote "\\.*/test/\\.*") (buffer-file-name (current-buffer)))))
"./gradlew build")
((eq major-mode 'web-mode)
"./gradlew compile-templates")
))
(defun my/test-command ()
"Returns a String representing the test command to run for the given context"
(cond
((eq major-mode 'js-mode) "grunt test") ;; Test the JS of the project
((eq major-mode 'java-mode) "./gradlew test") ;; Test the Java code of the project
((eq major-mode 'my-mode) "special-command.sh") ;; Even Special conditions/test-sets can be covered
))
(projectile-register-project-type 'has-command-at-point '("file.txt")
:compile 'my/compile-command
:test 'my/test-command)
If you would now navigate to a file that has the *.java extension under the ./tests/ directory and hit C-c p c you
will see ./gradlew build as the suggestion. If you were to navigate to a HTML file the compile command will have switched
to ./gradlew compile-templates.
This works for:
-
:configure -
:compile -
:compilation-dir -
:run
Note that your function has to return a string to work properly.
Related file location
The :test-prefix and :test-suffix will work regardless of file extension
or directory path and should be enough for simple projects. The
projectile-other-file-alist variable can also be set to find other files
based on the extension.
For fine-grained control of implementation/test toggling, the :test-dir option
of a project may take a function of one parameter (the implementation
directory absolute path) and return the directory of the test file. This in
conjunction with the options :test-prefix and :test-suffix will then be
used to determine the full path of the test file. This option will always be
respected if it is set.
Similarly, the :src-dir option, the analogue of :test-dir, may also take a
function and exhibits exactly the same behaviour as above except that its
parameter corresponds to the directory of a test file and it should return the
directory of the corresponding implementation file.
It’s recommended that either both or neither of these options are set to functions for consistent behaviour.
Alternatively, for flexible file switching across a range of projects,
the :related-files-fn option set to a custom function or a
list of custom functions can be used. The custom function accepts the relative
file name from the project root and it should return related file information
as a plist with the following optional key/value pairs:
| Key | Value | Command applicable |
|---|---|---|
:impl |
matching implementation file if the given file is a test file |
projectile-toggle-between-implementation-and-test, projectile-find-related-file |
:test |
matching test file if the given file has test files. |
projectile-toggle-between-implementation-and-test, projectile-find-related-file |
:other |
any other files if the given file has them. |
projectile-find-other-file, projectile-find-related-file |
:foo |
any key other than above |
projectile-find-related-file |
For each value, following type can be used:
| Type | Meaning |
|---|---|
string / a list of strings |
Relative paths from the project root. The paths which actually exist on the file system will be matched. |
a function |
A predicate which accepts a relative path as the input and return t if it matches. |
nil |
No match exists. |
Notes:
-
For a big project consisting of many source files, returning strings instead of a function can be fast as it does not iterate over each source file.
-
There is a difference in behaviour between no key and
nilvalue for the key. Only when the key does not exist, other project options such as:test-prefixorprojectile-other-file-alistmechanism is tried. -
If the
:test-diroption is set to a function, this will take precedence over any value for:related-files-fnset whenprojectile-toggle-between-implementation-and-testis called.
Example - Same source file name for test and impl
(defun my/related-files (path)
(if (string-match (rx (group (or "src" "test")) (group "/" (1+ anything) ".cpp")) path)
(let ((dir (match-string 1 path))
(file-name (match-string 2 path)))
(if (equal dir "test")
(list :impl (concat "src" file-name))
(list :test (concat "test" file-name)
:other (concat "src" file-name ".def"))))))
(projectile-register-project-type
;; ...
:related-files-fn #'my/related-files)
With the above example, src/test directory can contain the same name file for test and its implementation file. For example, "src/foo/abc.cpp" will match to "test/foo/abc.cpp" as test file and "src/foo/abc.cpp.def" as other file.
Example - Different test prefix per extension
A custom function for the project using multiple programming languages with different test prefixes.
(defun my/related-files(file)
(let ((ext-to-test-prefix '(("cpp" . "Test")
("py" . "test_"))))
(if-let* ((ext (file-name-extension file))
(test-prefix (assoc-default ext ext-to-test-prefix))
(file-name (file-name-nondirectory file)))
(if (string-prefix-p test-prefix file-name)
(let ((suffix (concat "/" (substring file-name (length test-prefix)))))
(list :impl (lambda (other-file)
(string-suffix-p suffix other-file))))
(let ((suffix (concat "/" test-prefix file-name)))
(list :test (lambda (other-file)
(string-suffix-p suffix other-file))))))))
projectile-find-related-file command is also available to find and choose
related files of any kinds. For example, the custom function can specify the
related documents with ':doc' key. Note that projectile-find-related-file only
relies on :related-files-fn for now.
Related file custom function helper
:related-files-fn can accept a list of custom functions to combine the result
of each custom function. This allows users to write several custom functions
and apply them differently to projects.
Projectile includes a couple of helpers to generate commonly used custom functions.
| Helper name and params | Purpose |
|---|---|
groups KIND GROUPS |
Relates files in each group as the specified kind. |
extensions KIND EXTENSIONS |
Relates files with extensions as the specified kind. |
test-with-prefix EXTENSION PREFIX |
Relates files with prefix and extension as :test and :impl. |
test-with-suffix EXTENSION SUFFIX |
Relates files with suffix and extension as :test and :impl. |
Each helper means projectile-related-files-fn-helper-name function.
Example usage of projectile-related-files-fn-helpers
(setq my/related-files
(list
(projectile-related-files-fn-extensions :other '("cpp" "h" "hpp"))
(projectile-related-files-fn-test-with-prefix "cpp" "Test")
(projectile-related-files-fn-test-with-suffix "el" "_test")
(projectile-related-files-fn-groups
:doc
'(("doc/common.txt"
"src/foo.h"
"src/bar.h")))))
(projectile-register-project-type
;; ...
:related-files-fn my/related-files)
Editing Existing Project Types
You can also edit specific options of already existing project types:
(projectile-update-project-type
'sbt
:related-files-fn
(list
(projectile-related-files-fn-test-with-suffix "scala" "Spec")
(projectile-related-files-fn-test-with-suffix "scala" "Test"))
:test-prefix nil
:precedence 'high)
This will change the value of the related-files-fn option, remove the test-prefix option and :precedence 'high sets the sbt project type to be chosen in preference to other potentially clashing project types (a value 'low would do the opposite).
Removing a Project Type
If a built-in project type mis-detects your projects (e.g. a language manifest that also appears in subdirectories of a larger repo), you can unregister it entirely:
(projectile-remove-project-type 'nix)
Don’t try to disable a type by clearing its markers with
(projectile-update-project-type 'nix :marker-files nil). An empty marker
set matches every project rather than none, so the type would be detected
everywhere. Use projectile-remove-project-type instead.
|
:test-dir/:src-dir vs :related-files-fn
Whilst setting the :test-dir and :src-dir to strings is sufficient for most
purposes, using functions can give more flexibility. As an example consider
(also using f.el):
(defun my-get-python-test-file (impl-file-path)
"Return the corresponding test file directory for IMPL-FILE-PATH"
(let* ((rel-path (f-relative impl-file-path (projectile-project-root)))
(src-dir (car (f-split rel-path))))
(cond ((f-exists-p (f-join (projectile-project-root) "test"))
(projectile-complementary-dir impl-file-path src-dir "test"))
((f-exists-p (f-join (projectile-project-root) "tests"))
(projectile-complementary-dir impl-file-path src-dir "tests"))
(t (error "Could not locate a test file for %s!" impl-file-path)))))
(defun my-get-python-impl-file (test-file-path)
"Return the corresponding impl file directory for TEST-FILE-PATH"
(if-let* ((root (projectile-project-root))
(rel-path (f-relative test-file-path root))
(src-dir-guesses `(,(f-base root) ,(downcase (f-base root)) "src"))
(src-dir (cl-find-if (lambda (d) (f-exists-p (f-join root d)))
src-dir-guesses)))
(projectile-complementary-dir test-file-path "tests?" src-dir)
(error "Could not locate an impl file for %s!" test-file-path)))
(projectile-update-project-type
'python-pkg
:src-dir #'my-get-python-impl-dir
:test-dir #'my-get-python-test-dir)
This attempts to recognise projects using both test and tests as top level
directories for test files. An alternative using the related-files-fn option
could be:
(projectile-update-project-type
'python-pkg
:related-files-fn
(list
(projectile-related-files-fn-test-with-suffix "py" "_test")
(projectile-related-files-fn-test-with-prefix "py" "test_")))
In fact this is a lot more flexible in terms of finding test files in different locations, but will not create test files for you.
Default source and test directories
When a project type doesn’t specify :src-dir or :test-dir, Projectile falls
back to these defaults:
(setq projectile-default-src-directory "src/")
(setq projectile-default-test-directory "test/")
Custom test prefix/suffix functions
For advanced use cases you can replace the functions that determine the test file prefix and suffix. These receive the project type and should return the appropriate prefix or suffix string:
(setq projectile-test-prefix-function #'my-test-prefix)
(setq projectile-test-suffix-function #'my-test-suffix)
Customizing Project Detection
Project detection is pretty simple - Projectile just runs a list of
project detection functions
(projectile-project-root-functions) until one of them returns
a project directory.
This list of functions is customizable, and while Projectile has some defaults for it, you can tweak it however you see fit.
Let’s take a closer look at projectile-project-root-functions:
(defcustom projectile-project-root-functions
'(projectile-root-local
projectile-root-marked
projectile-root-bottom-up
projectile-root-top-down
projectile-root-top-down-recurring)
"A list of functions for finding project roots."
:group 'projectile
:type '(repeat function))
The important thing to note here is that the functions get invoked in their order on the list, so the functions earlier in the list will have a higher precedence with respect to project detection. Let’s examine the defaults:
-
projectile-root-locallooks for project path set via the buffer-local variableprojectile-project-root. Typically you’d set this variable via.dir-locals.eland it will take precedence over everything else. -
projectile-root-markedlooks for.projectile(or whatever you’ve set as the value ofprojectile-dirconfig-file). The idea is that normally if you have a.projectilefile you’d like it to override the normal project root discovery logic. -
projectile-root-bottom-upwill start looking for a project marker file/folder (e.g..projectile,.hg,.git) from the current folder (a.k.a.default-directoryin Emacs lingo) up the directory tree. It will return the first match it discovers. The bottom-most (closest to the current dir) match wins. The list it searches (projectile-project-root-files-bottom-up) holds only VCS markers, so in a monorepo layout where a.gitsits at the top and a language manifest lives in a subdirectory, the enclosing repository wins - the git repo is the project, matching what most users and IDEs expect. If you instead want a deeper subproject to win, drop a.projectilefile in it;projectile-root-markedruns before this step, so the marked subproject takes precedence over the outer VC root. -
projectile-root-top-downis similar, but it will return the top-most (farthest from the current directory) match. It is configurable viaprojectile-project-root-files, which is populated automatically from the project types registered viaprojectile-register-project-type(each type’s:project-file, or the first of its marker files when no:project-fileis given). It is consulted after the bottom-up search fails, so it’s where the per-language manifests (deps.edn,Cargo.toml,pom.xml,?*.csproj, …) actually get matched. -
projectile-root-top-down-recurringwill look for project markers that can appear at every level of a project (e.g.Makefileor.svn) and will return the top-most match for those.
projectile-root-top-down only matches regular files — directories
with names listed in projectile-project-root-files are skipped. This is
why the default seed list contains files like configure.ac or TAGS
rather than VCS directories. projectile-root-bottom-up matches both
files and directories, so VCS markers like .git (a directory in normal
repos, a file in worktrees and submodules) belong on the bottom-up list.
|
The default ordering should work well for most people, but depending on the structure of your project you might want to tweak it.
Re-ordering those functions will alter the project detection, but you can also
replace the list. Here’s how you can delegate the project detection to Emacs’s
built-in function vc-root-dir:
;; we need this wrapper to match Projectile's API
(defun projectile-vc-root-dir (dir)
"Retrieve the root directory of the project at DIR using `vc-root-dir'."
(let ((default-directory dir))
(vc-root-dir)))
(setq projectile-project-root-functions '(projectile-vc-root-dir))
Similarly, you can leverage the built-in project.el like this:
;; we need this wrapper to match Projectile's API
(defun projectile-project-current (dir)
"Retrieve the root directory of the project at DIR using `project-current'."
(cdr (project-current nil dir)))
(setq projectile-project-root-functions '(projectile-project-current))
Project root cache
To keep projectile-project-root cheap (it’s called from the mode-line and
several find-file-hook paths), Projectile memoizes the result of every
project root function in the variable projectile-project-root-cache.
The cache is populated lazily and only invalidated when:
-
You call
projectile-invalidate-cache(s-p iby default), which clears both the per-project files cache and the project root cache. The root cache is cleared even when you cancel the project prompt or aren’t in a project, so this is also the right command to run after creating a new.projectile/.git/etc. in a directory that Projectile previously considered rootless. -
You call
projectile-discard-root-cacheif you want to clear only the project root cache without dropping the per-project file lists - useful when you’ve just added a marker file and don’t want to re-index large projects. -
You restart Emacs.
The cache is keyed on the search start directory and a positive entry is
revalidated against the filesystem (via file-exists-p) on every lookup, so
a deleted root naturally invalidates itself. Negative entries (no project
found) are also memoized to avoid re-walking the directory tree on every
call; this is the main source of confusion when adding a marker file:
Projectile remembers that the directory was rootless and won’t notice the
new marker until you invalidate the cache.
If you frequently script Projectile from elisp and need finer-grained
control, you can clear individual entries with remhash against the cache,
or reset the whole thing with (setq projectile-project-root-cache
(make-hash-table :test 'equal)).
|
The buffer-local file variable |
Ignoring files
Ignoring files using .projectile (a.k.a. dirconfig)
The contents of .projectile are ignored when using the
alien project indexing method.
|
If you’d like to instruct Projectile to ignore certain files in a
project, when indexing it you can do so in the .projectile file by
adding each path to ignore, where the paths all are relative to the
root directory and start with a slash. Everything ignored should be
preceded with a - sign.
Lines without any prefix at all are still accepted and treated
as ignore patterns for backward compatibility, but the implicit form
is being phased out and Projectile now warns about it once per
project. Prefer the explicit - prefix in new dirconfigs.
|
Here’s an example for a typical Rails application:
-/log -/tmp -/vendor -/public/uploads
This would ignore the folders only at the root of the project. Projectile also supports relative pathname ignores:
-tmp -*.rb -*.yml -models
You can also ignore everything except certain subdirectories. This is useful when selecting the directories to keep is easier than selecting the directories to ignore, although you can do both. To select directories to keep, that means everything else will be ignored.
Example:
+/src/foo +/tests/foo
Keep in mind that you can only include subdirectories, not file patterns.
If both directories to keep and ignore are specified, the directories to keep first apply, restricting what files are considered. The paths and patterns to ignore are then applied to that set.
Finally, you can override ignored files. This is especially useful when some files ignored by your VCS should be considered as part of your project by projectile:
!/src/foo !*.yml
When a path is overridden, its contents are still subject to ignore patterns. To override those files as well, specify their full path with a bang prefix.
Path entries vs. glob patterns
The two ignore examples above look similar but go through different
matchers. An entry that begins with a slash (e.g. -/log,
+/src/foo, !/src/foo) is treated as a path relative to the
project root and is expanded literally. An entry without a leading
slash (e.g. -tmp, -.rb) is treated as a *glob pattern applied
to every file’s path. As a consequence:
-
-/logignores only the top-levellogdirectory. -
-logignores anything calledlogat any depth, but only matches full path components — it will not matchxlogorlog.txt. -
-.rbignores any file whose path matches the glob.rb.
If a glob pattern doesn’t behave the way you’d expect — particularly across nested directories — try the explicit path form first to confirm whether the file is being indexed at all.
Comments
If you would like to include comment lines in your .projectile file,
you can customize the variable projectile-dirconfig-comment-prefix.
Assigning it a non-nil character value, e.g. #, will cause lines in
the .projectile file whose first non-whitespace character matches
that character to be treated as comments instead of patterns.
The same is true of the +, -, and ! prefixes: leading spaces
and tabs before the prefix are skipped, so accidental indentation
won’t silently turn the entry into a literal ignore pattern.
Ignored files using the project indexing tools
If you’re using the hybrid or alien indexing strategies, the simplest
way to ignore some files is just leverage the configuration of the
tool you’re using to do the project indexing.
E.g. in the case of git you can just tweak .gitignore.
Sometimes, however, you’d like to have some files as part of your project, but you don’t want to see them in Projectile for whatever reasons.
In those cases the project dirconfig file (.projectile) can be a handy
way to further adjust what you want to see in Projectile.
Global ignore and unignore settings
In addition to per-project ignores, Projectile provides several variables for
globally ignoring files and directories. These take effect with native and
hybrid indexing but are not applied with the alien indexing method.
;; Ignore files by suffix (e.g. compiled artifacts)
(setq projectile-globally-ignored-file-suffixes '(".o" ".pyc" ".elc"))
;; Ignore files matching regexp patterns
(setq projectile-global-ignore-file-patterns '("\\.min\\.js$" "\\.map$"))
Anchored vs anywhere directory ignores
projectile-globally-ignored-directories distinguishes between anchored
entries (matched as a path prefix relative to the project root) and anywhere
entries (matched at any depth in the tree). The leading * is not a glob
character — it is the marker that promotes an entry from anchored to anywhere.
(setq projectile-globally-ignored-directories
'("tmp" ; only ignores ./tmp at the project root
"*node_modules" ; ignores any directory named node_modules at any depth
))
The * prefix only matters for the hybrid post-processor; in native
indexing every entry is matched by directory basename at every traversal step
(so tmp and *tmp behave the same). alien ignores both forms entirely.
You can also unignore specific files or directories that would otherwise be excluded. This is useful when your VCS ignores files that you still want Projectile to show:
;; Unignore specific files
(setq projectile-globally-unignored-files '("important.dat"))
;; Unignore specific directories
(setq projectile-globally-unignored-directories '("vendor"))
File-local project root definitions
If you want to override the projectile project root for a specific
file, you can set the file-local variable projectile-project-root. This
can be useful if you have files within one project that are related to
a different project (for instance, Org files in one git repo that
correspond to other projects).
;; -*- projectile-project-root: "/path/to/other/project/" -*-
Override values are read from the buffer-local variable on every lookup (they’re intentionally exempt from the project root cache), so two buffers in the same directory can have different overrides and each will resolve to its own root.
Storing project settings
From project to project, some things may differ even in the same
language - coding styles, auto-completion sources, etc. If you need
to set some variables according to the selected project, you can use a
standard Emacs feature called
Per-directory Local Variables.
To use it you must create a file named .dir-locals.el (as specified
by the constant dir-locals-file) inside the project directory. This
file should contain something like this:
((nil . ((secret-ftp-password . "secret")
(compile-command . "make target-x")
(eval . (progn
(defun my-project-specific-function ()
;; ...
)))))
(c-mode . ((c-file-style . "BSD"))))
The top-level alist member referenced with the key nil applies to
the entire project. A key with the name eval will evaluate its
corresponding value. In the example above, this is used to create a
function. It could also be used to e.g. add such a function to a key
map.
You can also quickly visit or create the dir-locals-file with
s-p E (M-x projectile-edit-dir-locals RET). 3rd party packages may use functions projectile-add-dir-local-variable
and projectile-delete-dir-local-variable to store their settings.
|
Here are a few examples of how to use this feature with Projectile.
Configuring Projectile’s Behavior
Projectile exposes many variables (via defcustom) which allow users
to customize its behavior. Directory variables can be used to set
these customizations on a per-project basis.
You could enable caching for a project in this way:
((nil . ((projectile-enable-caching . t))))
If one of your projects had a file that you wanted Projectile to ignore, you would customize Projectile by:
((nil . ((projectile-globally-ignored-files . ("MyBinaryFile")))))
If you wanted to wrap the git command that Projectile uses to list the files in you repository, you could do:
((nil . ((projectile-git-command . "/path/to/other/git ls-files -zco --exclude-standard"))))
If you want to use a different project name than how Projectile named your project, you could customize it with the following:
((nil . ((projectile-project-name . "your-project-name-here"))))
By default, compilation buffers are not writable, which allows you to
e.g. press g to restart the last command. Setting
projectile-<cmd>-use-comint-mode (where <cmd> is configure,
compile, test, install, package, or run) to a non-nil value
allows you to make projectile compilation buffers interactive, letting
you e.g. test a command-line program with projectile-run-project.
(setq projectile-comint-mode t)
Project Buffers
Projectile offers a bunch of operations that are operating on the open buffers
for some project (e.g. projectile-kill-buffers). One tricky part here are
"special buffers" - basically buffers that are not backed by files
(e.g. *dired*, *scratch* and so on). Projectile determines whether a
special buffer belongs to a project simply by checking the default-directory
for the special buffer, which admittedly might result in some weird results
(e.g. if you’ve created a special buffer that’s not related to a project, while
visiting a file belonging to the project).
That’s why Projectile has a couple of configuration options for dealing with
project buffers - namely projectile-globally-ignored-buffers and
projectile-globally-ignored-modes. Both of them take a list of strings or
regular expressions that will be used to match against a buffer’s name or a
buffer’s major mode.
Here are a couple of examples:
;; ignoring specific buffers by name
(setq projectile-globally-ignored-buffers
'("*scratch*"
"*lsp-log*"))
;; ignoring buffers by their major mode
(setq projectile-globally-ignored-modes
'("erc-mode"
"help-mode"
"completion-list-mode"
"Buffer-menu-mode"
"gnus-.*-mode"
"occur-mode"))
Buffer filtering
You can supply a custom filter function for projectile-project-buffers via
projectile-buffers-filter-function. Projectile ships with two built-in
filters you can use:
;; Only include file-backed buffers
(setq projectile-buffers-filter-function #'projectile-buffers-with-file)
;; Include file-backed buffers and process buffers (e.g. REPLs, shells)
(setq projectile-buffers-filter-function #'projectile-buffers-with-file-or-process)
Killing project buffers
When you run projectile-kill-buffers (s-p k), the variable
projectile-kill-buffers-filter controls which buffers get killed:
;; Kill all project buffers (the default)
(setq projectile-kill-buffers-filter 'kill-all)
;; Kill only file-visiting buffers (keep shells, REPLs, etc.)
(setq projectile-kill-buffers-filter 'kill-only-files)
You can also set it to a custom predicate function that receives a buffer and returns non-nil if the buffer should be killed.
For finer control there’s a small composable condition DSL (modeled on
project.el’s `project-kill-buffer-conditions). Set the filter to a list of
conditions and a buffer is killed when it matches any of them:
;; Kill file-visiting buffers and dired buffers, but leave everything else alone
(setq projectile-kill-buffers-filter
'(buffer-file-name (derived-mode . dired-mode)))
Each condition is a buffer-name regexp, a predicate function, or a cons cell:
(major-mode . MODE), (derived-mode . MODE), (not COND), (and COND…),
or (or COND…). The last three nest, so you can build arbitrarily specific
rules. See the docstring of projectile-kill-buffers-filter for the details.
Uniquifying buffer names by project
When you open same-named files from different projects (say two README.md
files), Emacs’s uniquify library disambiguates the buffer names using parent
directory components. Projectile can make that disambiguation use the project
name instead, via projectile-uniquify-dirname-transform:
(setq uniquify-dirname-transform #'projectile-uniquify-dirname-transform)
Outside of a project the directory is left untouched, so this is safe to set
globally. This mirrors project.el’s `project-uniquify-dirname-transform.
Configure a Project’s Lifecycle Commands and Other Attributes
There are a few variables that are intended to be customized via .dir-locals.el.
-
for configuration -
projectile-project-configure-cmd -
for compilation -
projectile-project-compilation-cmd -
for testing -
projectile-project-test-cmd -
for installation -
projectile-project-install-cmd -
for packaging -
projectile-project-package-cmd -
for running -
projectile-project-run-cmd -
for configuring the test prefix -
projectile-project-test-prefix -
for configuring the test suffix -
projectile-project-test-suffix -
for configuring the related-files-fn property -
projectile-project-related-files-fn -
for configuring the src-dir property -
projectile-project-src-dir -
for configuring the test-dir property -
projectile-project-test-dir
When these variables have their default value of nil, Projectile
runs the default command for the current project type. You can
override this behavior by setting them to either a string to run an
external command or an Emacs Lisp function:
(setq projectile-test-cmd #'custom-test-function)
In addition caching of commands can be disabled by setting the variable
projectile-project-enable-cmd-caching to nil. This is useful for
preset-based CMake projects.
Because the cached command takes precedence over the value from
.dir-locals.el (and the project type default), editing those won’t take
effect until the cache is refreshed. Run projectile-discard-command-cache
to drop the cached commands for the current project so they’re re-read on the
next run. You can also wire it into an after-save-hook for .dir-locals.el
buffers if you edit them often.
By default, Projectile will not add consecutive duplicate commands to its
command history. To alter this behaviour you can use projectile-cmd-hist-ignoredups.
The default value of t means consecutive duplicates are ignored, a value
of nil means nothing is ignored, and a value of 'erase' means only
the last duplicate is kept in the command history.
Each command type (configure, compile, test, install, package, run) keeps its
own command history, so when you press M-p at, say, the test prompt you
only cycle through previous test commands instead of a mix of every command
you’ve run in the project. projectile-repeat-last-command still re-runs the
most recent command of any type.