Back to Table of Contents

Exploring large projects with Projectile and Helm Projectile

Table of Contents

Let's start with a few demos.

Demos

Select and open multiple files

Demo (begins when START DEMO appears in minibuffer):

helm-projectile-find-files-1.gif

In the demo, a few files are narrowed and marked, then opened. I ran helm-mini to display a list of opened txt files.

Open file at point anywhere

Demo (begins when START DEMO appears in minibuffer):

helm-projectile-find-files-dwim-1.gif

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):

helm-projectile-find-file-copy.gif

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.

Delete files anywhere

Demo (begins when START DEMO appears in minibuffer):

helm-projectile-find-file-delete.gif

As you see, you don't have to navigate to a file to delete it. All project files are always at your finger tip for you to delete!

Switch between current file and other files with same names but different extensions

Demo (begins when START DEMO appears in minibuffer):

helm-projectile-find-other-file.gif

  • 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 and bazaar are considered projects. So are lein, maven, sbt, scons, rebar and bundler. 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.

helm-projectile-switch-project.gif

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 is projectile-find-file. My suggestion is to bind it to helm-projectile-find-file, as it provides the same thing as projectile-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 is helm-projectile, this can be done: open files in other projects without ever leaving current working project. It is achieved by opening another helm-projectile session, but for another project, because helm-projectile always includes a list of projects, and makes helm-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):

    helm-projectile-1.gif

    • First, from the file MAINTAINERS, I ran helm-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. Normal projectile-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 as grep the whole project or any other actions you learn in this section.
  • 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):

    helm-projectile-remove-project.gif

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):

    helm-projectile-find-file-other-window.gif

    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 variant projectile-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):

    helm-projectile-find-file-as-root.gif

    In the demo, I opened directory /etc after I moved to it. No need to enter Tramp syntax for sudo 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):

    helm-projectile-rename-file.gif

    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):

    helm-projectile-serial-rename-file.gif

  • 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, recursively grep 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 invokes grep 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):

    helm-projectile-find-file-ediff.gif

  • 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 an Emerge 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):

    helm-projectile-etags.gif

  • 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.

Command: projectile-purge-file-from-cache

Usage: Remove a file from the cache. Once removed, you won't see it appear the next time using file related commands. If you delete a file, Projectile automatically removes the file from the cache.

Command: projectile-purge-dir-from-cache

Usage: Remove a directory from the 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 or helm-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):

    helm-projectile-new-virtual-dir.gif

  • 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 and helm-projectile-find-dir commands to update the virtual Dired buffer.

    Demo (begins when START DEMO appears in minibuffer):

    helm-projectile-add-files-virtual-dir.gif

  • 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 or helm-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):

    helm-projectile-delete-files-virtual-dir.gif

    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 for login is in handle/login.rb, the gui for login is in gui/login.rb and some project libraries that login uses in lib/. 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.

Buffer management

Command: helm-projectile-switch-to-buffer, C-c p b

Usage: List all opened buffers in current project. The command has a similar subset of actions in helm-projectile-find-file, so once you mastered the actions in helm-projectile-find-file, except instead of opening files, you open buffers instead.

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):

helm-projectile-grep.gif

You can specify directory to exclude when searching by customize either one of these variables:

  • grep-find-ignored-files: List of file names which rgrep and lgrep shall exclude. helm-projectile-grep also uses this variable.
  • grep-find-ignored-directories: List of names of sub-directories which rgrep 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 which rgrep and lgrep shall exclude, and helm-projectile-ack also uses this variable.
  • grep-find-ignored-directories: List of names of sub-directories which rgrep 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 which rgrep and lgrep shall exclude, and helm-projectile-ack also uses this variable.
  • grep-find-ignored-directories: List of names of sub-directories which rgrep 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
comments powered by Disqus