Exploring large projects with Projectile and Helm Projectile
Table of Contents
- Demos
- What is Projectile?
- Installation and configuration
- All-in-one command:
helm-projectile
, C-c p h - Enter project portal:
helm-projectile-switch-project
, C-c p p - File management
- Virtual directory manager
- Buffer management
- Search in project
- Summary of Keybindings
Let's start with a few demos.
Demos
Open file at point anywhere
Demo (begins when START DEMO
appears in minibuffer):
As you can see in the demo, Projectile can jump anywhere, even if there's only a filename without path. It works everywhere, even in text file.
- In the demo, the first path is a file that I opened using a command from M-x.
- The second path is a directory that I opened using a key binding.
- The third path is highlighted in a region and I opened using command history in Helm, so no need to type anything.
Copy files anywhere
Demo (begins when START DEMO
appears in minibuffer):
In the demo, I marked a few files with helm-projectile-find-file
,
then switched to action menu and selected Copy file(s)
action (it
can be executed with a key binding, but I want to show you the action
as a proof). Then, I copy to the directory ~/test
. Notice that I got
prompted to that directory immediately, because another Dired buffer
is below. After I confirmed the action, 4 files got copied and
displayed at the lower Dired buffer of ~/test
.
Switch between current file and other files with same names but different extensions
Demo (begins when START DEMO
appears in minibuffer):
- First, I select
helm-projectile-find-other-file
and a list of other files displayed. - Then, I marked a few files and press RET to open all.
- Finally, I use
helm-mini
to open a list of opened buffers and the files I marked and opened are there.
NOTE: In the demo, you can see me narrow to a small set of files, then mark them and perform some action (such as copy or delete). However, if you want to continue to mark other files, you can delete the previous search pattern and enter another search pattern to narrow to another set of files and continue marking those files without losing your old markings. For example, at the beginning I want to mark files with the string "aaaa" in it; after done marking those files, I delete "aaaa" and enter "bbbb" to narrow to this set of files and mark the needed files, without losing the markings on the files contains "aaaa".
What is Projectile?
From the homepage:
Projectile is a project interaction library for Emacs. Its goal is to provide a nice set of features operating on a project level without introducing external dependencies(when feasible). For instance - finding project files has a portable implementation written in pure Emacs Lisp without the use of GNU find (but for performance sake an indexing mechanism backed by external commands exists as well).
Projectile tries to be practical - portability is great, but if some external tools could speed up some task substantially and the tools are available, Projectile will leverage them.
By default,
git
,mercurial
,darcs
andbazaar
are considered projects. So arelein
,maven
,sbt
,scons
,rebar
andbundler
. If you want to mark a folder manually as a project just create an empty.projectile
file in it.
I will help you quickly benefit from Projectile and
helm-projectile
- an extension of Projectile that uses helm
interface for many Projectile commands that provides even more
features not available in stock Projectile - in a practical task
like exploring the Linux kernel.
This tutorial is about how to use Projectile along with Helm. There
are other completion systems such as grizzl
or ido
with flx
, both use
fuzzy matching. Helm also does almost the same thing, except that if
you insert a space anywhere in the prompt, Helm reverts back to its
old matching behavior: exact matching. Otherwise, Helm uses
fuzzy-matching by default, and it works with many tens of thousands of
files.
Installation and configuration
You also install Projectile via MELPA and setup:
(projectile-global-mode) (setq projectile-completion-system 'helm) (helm-projectile-on)
All Projectile commands has prefix C-c p
.
FOR WINDOWS USERS:
According to Projectile homepage:
Projectile has two modes of operation - one is portable and is implemented in Emacs Lisp(therefore it's native to Emacs and is known as the native indexing method) and the other relies on external commands like find, git, etc to obtain the list of files in a project.
Since the native indexing mode is much slower, by default the second method is used on all operating systems except Windows.
Using Emacs Lisp for indexing files is really slow on Windows. To enable external indexing, add this setting:
(setq projectile-indexing-method 'alien)
The alien indexing method uses external tools (e.g. git, find, etc) to speed up the indexing process.
All-in-one command: helm-projectile
, C-c p h
Usage: This command, by default, is the combination of these 5 commands:
helm-projectile-switch-to-buffer
helm-projectile-find-file
helm-projectile-switch-project
Enter project portal: helm-projectile-switch-project
, C-c p p
Usage: This is the very first command you need to use before using other
commands, because it is the entrance to all of your projects and the
only command that can be used outside of a project, aside from
helm-projectile-find-file-in-known-projects
. The command lists all
visited projects. If you first use Projectile, you have to visit at
least a project supported by Projectile to let it remember the
location of this project. The next time you won't have to manually
navigate to that project but jump to it instantly using
helm-projectile-switch-project
.
Available actions:
- Switch to project (default action, bound to RET): Switch to a
project and execute an action specified in
projectile-switch-project-action
variable. This variable stores a command to be executed after a project is selected. The default isprojectile-find-file
. My suggestion is to bind it tohelm-projectile-find-file
, as it provides the same thing asprojectile-find-file
but with more feature:(setq projectile-switch-project-action 'helm-projectile-find-file)
Even better, you should bind it to
helm-projectile
. When the action ishelm-projectile
, this can be done: open files in other projects without ever leaving current working project. It is achieved by opening anotherhelm-projectile
session, but for another project, becausehelm-projectile
always includes a list of projects, and makeshelm-projectile
list files in that project. This is not possible with normal Projectile with other completion systems, because other completion systems can only display one list at a time:(setq projectile-switch-project-action 'helm-projectile)
Demo (begin when
START DEMO
appears in minibuffer):- First, from the file
MAINTAINERS
, I ranhelm-projectile
. Notice that the current project I'm working is at the top of project list. - Then, I moved the highlight bar to
~/.emacs.d
project and press RET. Now,~/.emacs.d
is at the top of project list, indicating it is inside that project. Normalprojectile-switch-project
command does not display the current project, but Helm version displays it because you can perform many other useful actions with project root directory, such asgrep
the whole project or any other actions you learn in this section.
- First, from the file
- Open Dired in project's directory (C-d)
- Open project root in vc-dir or magit (M-g)
- Switch to Eshell (M-e): Open a projectin Eshell.
- Grep in projects (C-s; add prefix C-u to recursive grep): As you type the regexp in the mini buffer, the live grep results will be displayed incrementally.
- Compile project (C-c): Run a
compile
command at the project root. - Remove project(s) (M-D): Delete marked projects from the list of
known projects.
Demo (begins when
START DEMO
appears in minibuffer):
File management
Command: helm-projectile-find-file
, C-c p f
Usage: This command lists all files in a project for users to narrow
down to wanted files. Some frequently used actions that cover open,
rename, copy, delete,search and other miscelaneous operations. Once
you mastered the actions of helm-projectile-find-file
, you master
the actions of other commands as well since the actions of other
commands are just a subset of helm-projectile-find-file
actions. All
the key bindings associated with actions are only available while a
Helm buffer is active. You can think of actions as an mini version of
M-x: only applicable commands are listed, and even those commands
have key bindings. Prefix argument can be applied, when possible.
The same Helm interface can be used to search for an action. The first 12 actions are bound from <f1> to <f12>. You can type the index number to instantly narrow to that action, or simply press respective key.
Open
- Find File (default action bound to RET): open files; if multiple files are marked, using either M-SPC to mark specific files or all marked using M-a, all marked files are opened, as in the Select and open multiple files section.
- Find file other window (C-c o): Open file in other window. Very
useful action and is used in many Helm commands.
Demo (begins when
START DEMO
appears in minibuffer):Notice the filename in other window.
Normal Projectile commands have variants for opening file/directory/buffer in other window with prefix C-c 4 p. However, you have to make a mental choice which variant to use. If you already execute
projectile-find-file
command, and suddenly you decided to open in other window, you have to cancel current command and execute the whole thing with the other window variantprojectile-find-file-other-window
again. Using Helm, you don't have to worry about open in current window or other window first; you worry about that later when you already decided exact files to open. - Find file as root (C-c r): Another really useful action. With
this command, you don't have to use Tramp syntax to open file as
root. Just browse file to anywhere, and when needed, open it as root
instantly.
Demo (begins when
START DEMO
appears in minibuffer):In the demo, I opened directory
/etc
after I moved to it. No need to enter Tramp syntax forsudo
with full path again. - Find file other frame (C-c C-o): Open file in another frame.
- Find File in Dired: Open file directory in Dired.
- Find file in hex dump: Open file using hexl.
- View file: Open file for read-only.
- Open file externally (C-c C-x, add prefix C-u to choose a program): Open file using external applications. Once an application is selected, it is remembered as default application for the selected file type.
Move and Rename
- Rename file(s) (M-R): Rename marked files. To mark files, press
M-SPC. You must have two buffers side by side: one is a buffer
that is running current
helm-projectile-find-file
command and another is destination buffer. When this action is executed, it copies marked files to the directory of destination buffers.Demo (begins when
START DEMO
appears in minibuffer):In the demo, I selected a set of files in
helm-projectile-find-file
then press M-R to rename files to the directory of the right buffer,~/test_dir
. - Serial rename files: Rename multiple files at once to the same
name differentiated by the index at the end, and move files to a
prompted directory. If there is a buffer in other window, default to
the directory of that buffer.
Demo (begins when
START DEMO
appears in minibuffer): - Serial rename by symlinking files: Similar to
Serial rename files
but create symbolic links instead. - Serial rename by copying files: Similar to
Serial rename files
but copy files instead.
Copy and Delete
- Copy file(s) (M-C): similar to
Rename File(s)
action but copy marked files. You can stay where you are and select any project files from anywhere to copy to somewhere! The files are always at your finger tips. This is demonstrated at the beginning: Copy files anywhere. - Delete File(s) (M-D or C-c d): similar to
Rename File(s)
action but delete marked files. You can stay where you are and delete any file anywhere in your project. This is demonstrated at the beginning: Delete files anywhere.
Search and Replace
- Grep File(s) (C-s; add prefix C-u for recursive grep):
grep
current highlighted file or marked files. With prefix C-u, recursivelygrep
parent directories of marked files. Remember, it only works on marked files, or the current file the highlight bar is on. - Zgrep (M-g z; add prefix C-u for recursive zgrep): Similar to
grep
but invokesgrep
on compressed or gzipped files. - Locate (C-x C-f, add C-u to specify locate db): Search using
locate
, the same as helm-locate.
Miscelaneous
- Insert as org link (C-c @): Insert the current file that highlight bar is on as an Org link.
- Ediff File (C-=): If only a file is marked (that is the line
your Helm highlight bar is on), it prompts for another file to
compare. If two files are marked, starts an Ediff session between
two files. More than two files are marked, you are prompted for
another file to compare again.
Demo (begins when
START DEMO
appears in minibuffer): - Ediff Merge File (C-c =): Start an Emerge session between
selected files. Similar to
Ediff file
action: if one or more than two file are marked, prompts for another file. If exactly two files are selected, start anEmerge
session. - Etags (M-.): Invoke Etags using Helm. You can switch back to
helm-projectile-find-file
by pressing C-c p f while inside a Helm Etags session. If exists a symbol at point, only lists matches that contain the symbol.Demo (begins when
START DEMO
appears in minibuffer): - Switch to Eshell (M-e): Open Eshell in directory of the currently selected candidate. If selected candidate is a file, open the directory of that file; if selected candidate is a directory. open that directory.
- Eshell command on file(s) (M-!): Run an Eshell command on a marked candidates. If Eshell aliases exist, provides completion for those aliases.
- Symlink files(s) (M-S): Create symbolic link, using absolute
path. If another buffer is available, choose the directory of that
buffer as destination, similar to
Rename files(s)
action. - Relsymlink file(s): Create symbolic link, using relative path. If
another buffer is available, choose the directory of that buffer as
destination, similar to
Rename files(s)
action. - Hardlink file(s) (M-H): Create hard link. If another buffer is
available, choose the directory of that buffer as destination,
similar to
Rename files(s)
action. - Checksum File: Generate file checksum and insert the checksum
kill-ring
. - Print File (C-c p, add C-u to refresh): Print marked files.
Command: helm-projectile-find-file-in-known-projects
, C-c p F
This command is another one that can be used outside of any
project. When executed, it lists all files in all known
projects. Depends on your style, use this command or
helm-projectile-switch-project
command, when you want to jump to a
file. Note that this command could be slow to show you the list of
files if there is a large number of files. To speed it up, it is
beneficial to enable caching. You will learn about caching at near the
end of this tutorial. With caching, Projectile won't have to build up
a list of files again; it simply reuses, and show you the list
instantly for selecting.
The action menu is the same as helm-projectile-find-file
.
Command: helm-projectile-find-file-dwim
, C-c p g
Usage: Find file based on context at point (do what you mean):
- If the command finds just a file, it switches to that file instantly. This works even if the filename is incomplete, but there's only a single file in the current project that matches the filename at point. For example, if there's only a single file named "projectile/projectile.el" but the current filename is "projectile/proj" (incomplete), the command still switches to "projectile/projectile.el" immediately because this is the only filename that matches.
- If it finds a list of files, the list is displayed for selecting. A list of files is displayed when a filename appears more than one in the project or the filename at point is a prefix of more than two files in a project. For example, if `projectile-find-file' is executed on a path like "projectile/", it lists the content of that directory. If it is executed on a partial filename like "projectile/a", a list of files with character 'a' in that directory is presented.
- If it finds nothing, display a list of all files in project for selecting.
This command is demonstrated at the beginning: Open file at point anywhere.
Command: helm-projectile-find-dir
, C-c p d
Usage: List available directories in the current project.
Available actions:
- Open Dired in project's directory: Open the directory in a Dired buffer.
- Switch to Eshell (M-e): Open the directory in Eshell.
- Grep in projects (C-s; add prefix C-u for recurse Grep): Run
grep
on selected directory.
Command: helm-projectile-recentf
, C-c p e
Usage: List recently visited files in current project. The command has a
subset of actions in helm-projectile-find-file
, so once you mastered
the actions in helm-projectile-find-file
, you can reuse your
knowledge here.
Command: helm-projectile-find-other-file
, C-c p a
Usage: Switch between files with the same name but different extensions. With
prefix argument C-u, enable flex-matching that match any file that
contains the name of current file. The command has a subset of actions
in helm-projectile-find-file
, so once you mastered the actions in
helm-projectile-find-file
, you don't need to learn anything else.
Other file extensions can be customized with the variable
projectile-other-file-alist
. The variable looks like this:
'(("cpp" "h" "hpp" "ipp") ("ipp" "h" "hpp" "cpp") ("hpp" "h" "ipp" "cpp") ("cxx" "hxx" "ixx") ("ixx" "cxx" "hxx") ("hxx" "ixx" "cxx") ("c" "h") ("m" "h") ("mm" "h") ("h" "c" "cpp" "ipp" "hpp" "m" "mm") ("cc" "hh") ("hh" "cc") ("vert" "frag") ("frag" "vert") (nil "lock" "gpg") ("lock" "") ("gpg" ""))
Basically just a list of lists. Each lists hold the current file
extension as first element and other files' extensions to switch
to. For example, the list ("cpp" "h" "hpp" "ipp")
means that if your
current file is foo.cpp
, the command will search for other files
with foo
as exact name (add prefix C-u for any file that contains
foo
) and with extensions .h
, .hpp
and .ipp
; anything but .cpp
.
If you want to add more, for example, to switch between html
<->
js
, add to your init file like this:
(add-to-list 'projectile-other-file-alist '("html" "js")) ;; switch from html -> js (add-to-list 'projectile-other-file-alist '("js" "html")) ;; switch from js -> html
The command is already demonstrated in the section Switch between current file and other files with same names but different extensions.
Caching
Usage: In large projects, caching can significantly speedup file and directory listings, making it display instantly. Caching is enabled by:
(setq projectile-enable-caching t)
With caching enabled, even if you use Projectile on your home
directory with 30GB, it lists files instantly. Cache is a way to speed
up getting files because Projectile only needs to index your project
once and reuses this result future usages. In case if your project has
new files, you have to add C-u before executing any command to
invalidate the cache (except for helm-projectile-find-other-file
and
projectile-find-other-file
, C-u is reserved for different
behaviour), or using standalone command invalidate C-c p i to
refresh the whole cache.
Command: projectile-invalidate-cache
, C-c p i
Usage: As the command name suggests, it invalidates the current cache and retrieves everything as new.
Command: projectile-cache-current-file
, C-c p z
Usage: Add the file of current selected buffer to cache.
Virtual directory manager
Now that you know how to manage your project files with Helm and Projectile, it's time to explore this cool feature that is exclusive to Helm Projectile: Virtual Directory. A virtual directory is just a Dired buffer but with files from different directory location assembled into one buffer.
Purpose: Projectile is excellent for file browsing, and can access files anywhere in project. Dired is excellent because it can be used as a file browser, as well as by being a normal buffer, it can be saved as an entry in Bookmark for future sessions. We can combine get the best of both: a logical list of files from anywhere that can be saved for use in the future.
The following are actions to be used for managing a virtual directory
when you are in a helm-projectile-find-file
session:
- Create Dired buffer from files (C-c f): creates a virtual Dired
buffer that populates marked files into that Dired buffer. If the
current buffer is a Dired buffer, invoking
helm-projectile-find-file
orhelm-projectile-find-dir
also adds another list that presents all the current entries in current Dired buffer. You can create a totally new virtual Dired buffer from these entries as well.Demo (begins when
START DEMO
appears in minibuffer): - Add files to Dired buffer (C-c a): when the current buffer is an
existing Dired buffer, we can add files/directories from
helm-projectile-find-file
andhelm-projectile-find-dir
commands to update the virtual Dired buffer.Demo (begins when
START DEMO
appears in minibuffer): - Remove entry(s) from Dired buffer (C-c d): This command can only
be used when the current buffer is a Dired buffer. When the current
buffer is a Dired buffer, activating
helm-projectile-find-file
orhelm-projectile-find-dir
will add another list that presents all entries in the Dired buffer. We can mark entries, then press C-c d to delete all marked entries from the current Dired buffer.Demo (begins when
START DEMO
appears in minibuffer):These features are useful when you want to groups related files scattered across different directories in your project. For example, you have a feature called
login
. The logic forlogin
is inhandle/login.rb
, the gui for login is ingui/login.rb
and some project libraries that login uses inlib/
. You want to group these files together for according to any of your logical views as you see fit. You do not need to depend on the fixed layout of physical directory structure, that is the logic of someone else.
Store virtual directories with Bookmark (or Bookmark+)
Using Bookmark
(or Bookmark+
), you can actually save your virtual
Dired buffers and preserve this knowledge for future reference, when you
want to quickly review related files to improve or fix bug. You won't
have to reconnect related files again, and often it takes quite some
time since you could forget many things.
If you haven't learned how to use Bookmark
, learn the basics of it
with Xah Lee's "Emacs: Using Bookmark Feature" article. I recommend
you to install Bookmark+
because it offers much more features. For
example, you can write annotations for (C-u a on a bookmark)
bookmarks in Org-mode
and read the annotation with Org-mode
(press
a to open the annotation of a bookmark for reading). To learn all
about Bookmark+
features, please refer to Bookmark+ documentation.
Search in project
Command: helm-projectile-grep
, C-c p s g
This is a replacement command for projectile-grep
that uses Helm
interface. When a symbol is at point, this command uses that symbol
and search at project root for every occurrence of this symbol in all
non-ignored files in project. If a region is active, use the region
instead.
Demo (begins when START DEMO
appears in minibuffer):
You can specify directory to exclude when searching by customize either one of these variables:
grep-find-ignored-files
: List of file names whichrgrep
andlgrep
shall exclude.helm-projectile-grep
also uses this variable.grep-find-ignored-directories
: List of names of sub-directories whichrgrep
shall not recurse into.helm-projectile-grep
also uses this variable.projectile-globally-ignored-files
: A list of files globally ignored by Projectile.projectile-globally-ignored-directories
: A list of directories globally ignored by Projectile.
For example, if you have a directory like personal/backup
and if you
want to ignore directory backup
, simply add backup
to the
grep-find-ignored-directories
or
projectile-globally-ignored-directories
. For example, if I use
projectile-globally-ignored-directories
, I would do it like this:
(add-to-list 'projectile-globally-ignored-directories "backup")
You can also use C-h v and select either of the above variables and use customize GUI to add your selections.
NOTE: For helm-projectile-grep
, when specifying directories to
ignore, you must enter only the names of sub-directories, not its
full path, either absolute or relative to current project. In the
example above, I only add backup
, not personal/backup
.
Finally, if you quit your current helm-projectile-grep
session with
current search results and don't want to search things all over again,
you can save the result with the action Save results in grep buffer
(bound to <f3>
). Or you can simply resume the previous buffer with
helm-resume
(you should bind it to a key. If your follow my Helm
guide, it is bound to C-c h b); if you helm-projectile-grep
buffer
is not the most recently used Helm buffer, add C-u before running
helm-resume
to select it from the list of all previously Helm
buffers of all ran Helm commands.
Command: helm-projectile-ack
, C-c p s a
This is a replacement command for projectile-ack
. Similar to
helm-projectile-grep
, but use ack
instead. You must have ack
installed to be able to use this command. You can also customize these
variables to ignore files or directories:
grep-find-ignored-files
: List of file names whichrgrep
andlgrep
shall exclude, andhelm-projectile-ack
also uses this variable.grep-find-ignored-directories
: List of names of sub-directories whichrgrep
shall not recurse into.helm-projectile-ack
also uses this variable.projectile-globally-ignored-files
: A list of files globally ignored by Projectile.projectile-globally-ignored-directories
: A list of directories globally ignored by Projectile.
Similarly, you can save results in a grep buffer with the action Save
results in grep buffer
(bound to <f3>
), and resume with C-c h b
(add C-u to select from the list of all previous Helm sessions).
NOTE: if you want ack
to ignore files/directories, you must use
regex pattern. Otherwise it won't work.
Command: helm-projectile-ag
, C-c p s s
This is a replacement command for projectile-ag
. Similar to
helm-projectile-grep
, but use ag
insetad. You must have ag
and
helm-ag
installed to be able to use this command. This command only
works with symbol at point, but not region. You can also customize
these variables to ignore files or directories:
grep-find-ignored-files
: List of file names whichrgrep
andlgrep
shall exclude, andhelm-projectile-ack
also uses this variable.grep-find-ignored-directories
: List of names of sub-directories whichrgrep
shall not recurse into.helm-projectile-ack
also uses this variable.projectile-globally-ignored-files
: A list of files globally ignored by Projectile.projectile-globally-ignored-directories
: A list of directories globally ignored by Projectile.
NOTE: Unlike helm-projectile-grep
, you can specify directory with
path to ignore. For example, you can only specify sub-directory names
to ignore in helm-projectile-grep
, i.e. backup
not
personal/backup
, but with helm-projectile-ag
, either backup
or
personal/backup
works fine.
Summary of Keybindings
This chapter summarizes the key bindings introduced in the above chapters.
Key Binding | Command | Description |
---|---|---|
C-c p h |
helm-projectile |
Helm interface to projectile |
C-c p p |
helm-projectile-switch-project |
Switches to another projectile project |
C-c p f |
helm-projectile-find-file |
Lists all files in a project |
C-c p F |
helm-projectile-find-file-in-known-projects |
Find file in all known projects |
C-c p g |
helm-projectile-find-file-dwim |
Find file based on context at point |
C-c p d |
helm-projectile-find-dir |
Lists available directories in current project |
C-c p e |
helm-projectile-recentf |
Lists recently opened files in current project |
C-c p a |
helm-projectile-find-other-file |
Switch between files with same name but different extensions |
C-c p i |
projectile-invalidate-cache |
Invalidate cache |
C-c p z |
projectile-cache-current-file |
Add the file of current selected buffer to cache |
C-c p b |
helm-projectile-switch-to-buffer |
List all open buffers in current project |
C-c p s g |
helm-projectile-grep |
Searches for symbol starting from project root |
C-c p s a |
helm-projectile-ack |
Same as above but using ack |
C-c p s s |
helm-projectile-ag |
Same as above but using ag |