mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-18 c655eea759be1db69c5e6b45c228139d8390122a
commit | author | age
5cb5f7 1 ;;; projectile.el --- Manage and navigate projects in Emacs easily -*- lexical-binding: t -*-
C 2
3 ;; Copyright © 2011-2018 Bozhidar Batsov <bozhidar@batsov.com>
4
5 ;; Author: Bozhidar Batsov <bozhidar@batsov.com>
6 ;; URL: https://github.com/bbatsov/projectile
7 ;; Package-Version: 20181106.1631
8 ;; Keywords: project, convenience
9 ;; Version: 1.1.0-snapshot
10 ;; Package-Requires: ((emacs "25.1") (pkg-info "0.4"))
11
12 ;; This file is NOT part of GNU Emacs.
13
14 ;; This program is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 3, or (at your option)
17 ;; any later version.
18 ;;
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 ;; GNU General Public License for more details.
23 ;;
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
26 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
27 ;; Boston, MA 02110-1301, USA.
28
29 ;;; Commentary:
30 ;;
31 ;; This library provides easy project management and navigation.  The
32 ;; concept of a project is pretty basic - just a folder containing
33 ;; special file.  Currently git, mercurial and bazaar repos are
34 ;; considered projects by default.  If you want to mark a folder
35 ;; manually as a project just create an empty .projectile file in
36 ;; it.  See the README for more details.
37 ;;
38 ;;; Code:
39
40 (require 'cl-lib)
41 (require 'thingatpt)
42 (require 'ibuffer)
43 (require 'ibuf-ext)
44 (require 'compile)
45 (require 'grep)
46 (eval-when-compile
47   (require 'subr-x))
48
49 (eval-when-compile
50   (defvar ag-ignore-list)
51   (defvar ggtags-completion-table)
52   (defvar tags-completion-table)
53   (defvar tags-loop-scan)
54   (defvar tags-loop-operate)
55   (defvar eshell-buffer-name)
56   (defvar explicit-shell-file-name))
57
58 (declare-function ggtags-ensure-project "ggtags")
59 (declare-function ggtags-update-tags "ggtags")
60 (declare-function pkg-info-version-info "pkg-info")
61 (declare-function tags-completion-table "etags")
62 (declare-function make-term "term")
63 (declare-function term-mode "term")
64 (declare-function term-char-mode "term")
65 (declare-function eshell-search-path "esh-ext")
66 (declare-function vc-dir "vc-dir")
67 (declare-function vc-dir-busy "vc-dir")
68 (declare-function ripgrep-regexp "ripgrep")
69 (declare-function string-trim "subr-x")
70
71 (defvar grep-files-aliases)
72 (defvar grep-find-ignored-directories)
73 (defvar grep-find-ignored-files)
74
75
76 ;;; Customization
77 (defgroup projectile nil
78   "Manage and navigate projects easily."
79   :group 'tools
80   :group 'convenience
81   :link '(url-link :tag "GitHub" "https://github.com/bbatsov/projectile")
82   :link '(url-link :tag "Online Manual" "https://docs.projectile.mx/")
83   :link '(emacs-commentary-link :tag "Commentary" "projectile"))
84
85 (defcustom projectile-indexing-method (if (eq system-type 'windows-nt) 'native 'alien)
86   "Specifies the indexing method used by Projectile.
87
88 There are three indexing methods - native, hybrid and alien.
89
90 The native method is implemented in Emacs Lisp (therefore it is
91 native to Emacs).  Its advantage is that it is portable and will
92 work everywhere that Emacs does.  Its disadvantage is that it is a
93 bit slow (especially for large projects).  Generally it's a good
94 idea to pair the native indexing method with caching.
95
96 The hybrid indexing method uses external tools (e.g. git, find,
97 etc) to speed up the indexing process.  Still, the files will be
98 post-processed by Projectile for sorting/filtering purposes.
99 In this sense that approach is a hybrid between native and indexing
100 and alien indexing.
101
102 The alien indexing method optimizes to the limit the speed
103 of the hybrid indexing method.  This means that Projectile will
104 not do any processing of the files returned by the external
105 commands and you're going to get the maximum performance
106 possible.  This behaviour makes a lot of sense for most people,
107 as they'd typically be putting ignores in their VCS config and
108 won't care about any additional ignores/unignores/sorting that
109 Projectile might also provide.
110
111 The disadvantage of the hybrid and alien methods is that they are not well
112 supported on Windows systems.  That's why by default alien indexing is the
113 default on all operating systems, except Windows."
114   :group 'projectile
115   :type '(radio
116           (const :tag "Native" native)
117           (const :tag "Hybrid" hybrid)
118           (const :tag "Alien" alien)))
119
120 (defcustom projectile-enable-caching (eq projectile-indexing-method 'native)
121   "When t enables project files caching.
122
123 Project caching is automatically enabled by default if you're
124 using the native indexing method."
125   :group 'projectile
126   :type 'boolean)
127
128 (defcustom projectile-kill-buffers-filter 'kill-all
129   "Determine which buffers are killed by `projectile-kill-buffers'.
130
131 When the kill-all option is selected, kills each buffer.
132
133 When the kill-only-files option is selected, kill only the buffer
134 associated to a file.
135
136 Otherwise, it should be a predicate that takes one argument: the buffer to
137 be killed."
138   :group 'projectile
139   :type '(radio
140           (const :tag "All project buffers" kill-all)
141           (const :tag "Project file buffers" kill-only-files)
142           (function :tag "Predicate")))
143
144 (defcustom projectile-file-exists-local-cache-expire nil
145   "Number of seconds before the local file existence cache expires.
146 Local refers to a file on a local file system.
147
148 A value of nil disables this cache.
149 See `projectile-file-exists-p' for details."
150   :group 'projectile
151   :type '(choice (const :tag "Disabled" nil)
152                  (integer :tag "Seconds")))
153
154 (defcustom projectile-file-exists-remote-cache-expire (* 5 60)
155   "Number of seconds before the remote file existence cache expires.
156 Remote refers to a file on a remote file system such as tramp.
157
158 A value of nil disables this cache.
159 See `projectile-file-exists-p' for details."
160   :group 'projectile
161   :type '(choice (const :tag "Disabled" nil)
162                  (integer :tag "Seconds")))
163
164 (defcustom projectile-files-cache-expire nil
165   "Number of seconds before project files list cache expires.
166
167 A value of nil means the cache never expires."
168   :group 'projectile
169   :type '(choice (const :tag "Disabled" nil)
170                  (integer :tag "Seconds")))
171
172 (defcustom projectile-require-project-root 'prompt
173   "Require the presence of a project root to operate when true.
174 When set to 'prompt Projectile will ask you to select a project
175 directory if you're not in a project.
176
177 When nil Projectile will consider the current directory the project root."
178   :group 'projectile
179   :type '(choice (const :tag "No" nil)
180                  (const :tag "Yes" t)
181                  (const :tag "Prompt for project" prompt)))
182
183 (defcustom projectile-completion-system 'ido
184   "The completion system to be used by Projectile."
185   :group 'projectile
186   :type '(radio
187           (const :tag "Ido" ido)
188           (const :tag "Helm" helm)
189           (const :tag "Ivy" ivy)
190           (const :tag "Default" default)
191           (function :tag "Custom function")))
192
193 (defcustom projectile-keymap-prefix nil
194   "Projectile keymap prefix."
195   :group 'projectile
196   :type 'string)
197
198 (make-obsolete-variable 'projectile-keymap-prefix "Use (define-key projectile-mode-map (kbd ...) 'projectile-command-map) instead." "1.1.0")
199
200 (defcustom projectile-cache-file
201   (expand-file-name "projectile.cache" user-emacs-directory)
202   "The name of Projectile's cache file."
203   :group 'projectile
204   :type 'string)
205
206 (defcustom projectile-tags-file-name "TAGS"
207   "The tags filename Projectile's going to use."
208   :group 'projectile
209   :type 'string)
210
211 (defcustom projectile-tags-command "ctags -Re -f \"%s\" %s \"%s\""
212   "The command Projectile's going to use to generate a TAGS file."
213   :group 'projectile
214   :type 'string)
215
216 (defcustom projectile-tags-backend 'auto
217   "The tag backend that Projectile should use.
218
219 If set to 'auto', `projectile-find-tag' will automatically choose
220 which backend to use.  Preference order is ggtags -> xref
221 -> etags-select -> `find-tag'.  Variable can also be set to specify which
222 backend to use.  If selected backend is unavailable, fall back to
223 `find-tag'.
224
225 If this variable is set to 'auto' and ggtags is available, or if
226 set to 'ggtags', then ggtags will be used for
227 `projectile-regenerate-tags'.  For all other settings
228 `projectile-tags-command' will be used."
229   :group 'projectile
230   :type '(radio
231           (const :tag "auto" auto)
232           (const :tag "xref" xref)
233           (const :tag "ggtags" ggtags)
234           (const :tag "etags" etags-select)
235           (const :tag "standard" find-tag))
236   :package-version '(projectile . "0.14.0"))
237
238 (defcustom projectile-sort-order 'default
239   "The sort order used for a project's files."
240   :group 'projectile
241   :type '(radio
242           (const :tag "default" default)
243           (const :tag "recentf" recentf)
244           (const :tag "recently active" recently-active)
245           (const :tag "access time" access-time)
246           (const :tag "modification time" modification-time)))
247
248 (defcustom projectile-verbose t
249   "Echo messages that are not errors."
250   :group 'projectile
251   :type 'boolean)
252
253 (defcustom projectile-buffers-filter-function nil
254   "A function used to filter the buffers in `projectile-project-buffers'.
255
256 The function should accept and return a list of Emacs buffers.
257 Two example filter functions are shipped by default -
258 `projectile-buffers-with-file' and
259 `projectile-buffers-with-file-or-process'."
260   :group 'projectile
261   :type 'function)
262
263 (defcustom projectile-project-name nil
264   "If this value is non-nil, it will be used as project name.
265
266 It has precedence over function `projectile-project-name-function'."
267   :group 'projectile
268   :type 'string
269   :package-version '(projectile . "0.14.0"))
270
271 (defcustom projectile-project-name-function 'projectile-default-project-name
272   "A function that receives the project-root and returns the project name.
273
274 If variable `projectile-project-name' is non-nil, this function will not be used."
275   :group 'projectile
276   :type 'function
277   :package-version '(projectile . "0.14.0"))
278
279 (defcustom projectile-project-root-files
280   '("rebar.config"       ; Rebar project file
281     "project.clj"        ; Leiningen project file
282     "build.boot"         ; Boot-clj project file
283     "deps.edn"           ; Clojure CLI project file
284     "SConstruct"         ; Scons project file
285     "pom.xml"            ; Maven project file
286     "build.sbt"          ; SBT project file
287     "gradlew"            ; Gradle wrapper script
288     "build.gradle"       ; Gradle project file
289     ".ensime"            ; Ensime configuration file
290     "Gemfile"            ; Bundler file
291     "requirements.txt"   ; Pip file
292     "setup.py"           ; Setuptools file
293     "tox.ini"            ; Tox file
294     "composer.json"      ; Composer project file
295     "Cargo.toml"         ; Cargo project file
296     "mix.exs"            ; Elixir mix project file
297     "stack.yaml"         ; Haskell's stack tool based project
298     "info.rkt"           ; Racket package description file
299     "DESCRIPTION"        ; R package description file
300     "TAGS"               ; etags/ctags are usually in the root of project
301     "GTAGS"              ; GNU Global tags
302     "configure.in"       ; autoconf old style
303     "configure.ac"       ; autoconf new style
304     "cscope.out"         ; cscope
305     )
306   "A list of files considered to mark the root of a project.
307 The topmost match has precedence."
308   :group 'projectile
309   :type '(repeat string))
310
311 (defcustom projectile-project-root-files-bottom-up
312   '(".projectile" ; projectile project marker
313     ".git"        ; Git VCS root dir
314     ".hg"         ; Mercurial VCS root dir
315     ".fslckout"   ; Fossil VCS root dir
316     "_FOSSIL_"    ; Fossil VCS root DB on Windows
317     ".bzr"        ; Bazaar VCS root dir
318     "_darcs"      ; Darcs VCS root dir
319     )
320   "A list of files considered to mark the root of a project.
321 The bottommost (parentmost) match has precedence."
322   :group 'projectile
323   :type '(repeat string))
324
325 (defcustom projectile-project-root-files-top-down-recurring
326   '(".svn" ; Svn VCS root dir
327     "CVS"  ; Csv VCS root dir
328     "Makefile")
329   "A list of files considered to mark the root of a project.
330 The search starts at the top and descends down till a directory
331 that contains a match file but its parent does not.  Thus, it's a
332 bottommost match in the topmost sequence of directories
333 containing a root file."
334   :group 'projectile
335   :type '(repeat string))
336
337 (defcustom projectile-project-root-files-functions
338   '(projectile-root-local
339     projectile-root-bottom-up
340     projectile-root-top-down
341     projectile-root-top-down-recurring)
342   "A list of functions for finding project roots."
343   :group 'projectile
344   :type '(repeat function))
345
346 (defcustom projectile-globally-ignored-files
347   (list projectile-tags-file-name)
348   "A list of files globally ignored by projectile."
349   :group 'projectile
350   :type '(repeat string))
351
352 (defcustom projectile-globally-unignored-files nil
353   "A list of files globally unignored by projectile.
354
355 Regular expressions can be used."
356   :group 'projectile
357   :type '(repeat string)
358   :package-version '(projectile . "0.14.0"))
359
360 (defcustom projectile-globally-ignored-file-suffixes
361   nil
362   "A list of file suffixes globally ignored by projectile."
363   :group 'projectile
364   :type '(repeat string))
365
366 (defcustom projectile-globally-ignored-directories
367   '(".idea"
368     ".ensime_cache"
369     ".eunit"
370     ".git"
371     ".hg"
372     ".fslckout"
373     "_FOSSIL_"
374     ".bzr"
375     "_darcs"
376     ".tox"
377     ".svn"
378     ".stack-work")
379   "A list of directories globally ignored by projectile.
380
381 Regular expressions can be used."
382   :safe (lambda (x) (not (remq t (mapcar #'stringp x))))
383   :group 'projectile
384   :type '(repeat string))
385
386 (defcustom projectile-globally-unignored-directories nil
387   "A list of directories globally unignored by projectile."
388   :group 'projectile
389   :type '(repeat string)
390   :package-version '(projectile . "0.14.0"))
391
392 (defcustom projectile-globally-ignored-modes
393   '("erc-mode"
394     "help-mode"
395     "completion-list-mode"
396     "Buffer-menu-mode"
397     "gnus-.*-mode"
398     "occur-mode")
399   "A list of regular expressions for major modes ignored by projectile.
400
401 If a buffer is using a given major mode, projectile will ignore
402 it for functions working with buffers."
403   :group 'projectile
404   :type '(repeat string))
405
406 (defcustom projectile-globally-ignored-buffers nil
407   "A list of buffer-names ignored by projectile.
408
409 You can use either exact buffer names or regular expressions.
410 If a buffer is in the list projectile will ignore it for
411 functions working with buffers."
412   :group 'projectile
413   :type '(repeat string)
414   :package-version '(projectile . "0.12.0"))
415
416 (defcustom projectile-find-file-hook nil
417   "Hooks run when a file is opened with `projectile-find-file'."
418   :group 'projectile
419   :type 'hook)
420
421 (defcustom projectile-find-dir-hook nil
422   "Hooks run when a directory is opened with `projectile-find-dir'."
423   :group 'projectile
424   :type 'hook)
425
426 (defcustom projectile-switch-project-action 'projectile-find-file
427   "Action invoked after switching projects with `projectile-switch-project'.
428
429 Any function that does not take arguments will do."
430   :group 'projectile
431   :type 'function)
432
433 (defcustom projectile-find-dir-includes-top-level nil
434   "If true, add top-level dir to options offered by `projectile-find-dir'."
435   :group 'projectile
436   :type 'boolean)
437
438 (defcustom projectile-use-git-grep nil
439   "If true, use `vc-git-grep' in git projects."
440   :group 'projectile
441   :type 'boolean)
442
443 (defcustom projectile-grep-finished-hook nil
444   "Hooks run when `projectile-grep' finishes."
445   :group 'projectile
446   :type 'hook
447   :package-version '(projectile . "0.14.0"))
448
449 (defcustom projectile-test-prefix-function 'projectile-test-prefix
450   "Function to find test files prefix based on PROJECT-TYPE."
451   :group 'projectile
452   :type 'function)
453
454 (defcustom projectile-test-suffix-function 'projectile-test-suffix
455   "Function to find test files suffix based on PROJECT-TYPE."
456   :group 'projectile
457   :type 'function)
458
459 (defcustom projectile-dynamic-mode-line t
460   "If true, update the mode-line dynamically.
461 Only file buffers are affected by this, as the update happens via
462 `find-file-hook'.
463
464 See also `projectile-mode-line-function' and `projectile-update-mode-line'."
465   :group 'projectile
466   :type 'boolean
467   :package-version '(projectile . "1.1.0"))
468
469 (defcustom projectile-mode-line-function 'projectile-default-mode-line
470   "The function to use to generate project-specific mode-line.
471 The default function adds the project name and type to the mode-line.
472 See also `projectile-update-mode-line'."
473   :group 'projectile
474   :type 'function
475   :package-version '(projectile . "1.1.0"))
476
477
478 ;;; Idle Timer
479 (defvar projectile-idle-timer nil
480   "The timer object created when `projectile-enable-idle-timer' is non-nil.")
481
482 (defcustom projectile-idle-timer-seconds 30
483   "The idle period to use when `projectile-enable-idle-timer' is non-nil."
484   :group 'projectile
485   :type 'number)
486
487 (defcustom projectile-idle-timer-hook '(projectile-regenerate-tags)
488   "The hook run when `projectile-enable-idle-timer' is non-nil."
489   :group 'projectile
490   :type '(repeat symbol))
491
492 (defcustom projectile-enable-idle-timer nil
493   "Enables idle timer hook `projectile-idle-timer-functions'.
494
495 When `projectile-enable-idle-timer' is non-nil, the hook
496 `projectile-idle-timer-hook' is run each time Emacs has been idle
497 for `projectile-idle-timer-seconds' seconds and we're in a
498 project."
499   :group 'projectile
500   :set (lambda (symbol value)
501          (set symbol value)
502          (when projectile-idle-timer
503            (cancel-timer projectile-idle-timer))
504          (setq projectile-idle-timer nil)
505          (when projectile-enable-idle-timer
506            (setq projectile-idle-timer (run-with-idle-timer
507                                         projectile-idle-timer-seconds t
508                                         (lambda ()
509                                           (when (projectile-project-p)
510                                             (run-hooks 'projectile-idle-timer-hook)))))))
511   :type 'boolean)
512
513 (defvar projectile-projects-cache nil
514   "A hashmap used to cache project file names to speed up related operations.")
515
516 (defvar projectile-projects-cache-time nil
517   "A hashmap used to record when we populated `projectile-projects-cache'.")
518
519 (defvar projectile-project-root-cache (make-hash-table :test 'equal)
520   "Cached value of function `projectile-project-root`.")
521
522 (defvar projectile-project-type-cache (make-hash-table :test 'equal)
523   "A hashmap used to cache project type to speed up related operations.")
524
525 (defvar projectile-known-projects nil
526   "List of locations where we have previously seen projects.
527 The list of projects is ordered by the time they have been accessed.
528
529 See also `projectile-remove-known-project',
530 `projectile-cleanup-known-projects' and `projectile-clear-known-projects'.")
531
532 (defvar projectile-known-projects-on-file nil
533   "List of known projects reference point.
534
535 Contains a copy of `projectile-known-projects' when it was last
536 synchronized with `projectile-known-projects-file'.")
537
538 (defcustom projectile-known-projects-file
539   (expand-file-name "projectile-bookmarks.eld"
540                     user-emacs-directory)
541   "Name and location of the Projectile's known projects file."
542   :group 'projectile
543   :type 'string)
544
545 (defcustom projectile-ignored-projects nil
546   "A list of projects not to be added to `projectile-known-projects'."
547   :group 'projectile
548   :type '(repeat :tag "Project list" directory)
549   :package-version '(projectile . "0.11.0"))
550
551 (defcustom projectile-ignored-project-function nil
552   "Function to decide if a project is added to `projectile-known-projects'.
553
554 Can be either nil, or a function that takes the truename of the
555 project root as argument and returns non-nil if the project is to
556 be ignored or nil otherwise.
557
558 This function is only called if the project is not listed in
559 `projectile-ignored-projects'.
560
561 A suitable candidate would be `file-remote-p' to ignore remote
562 projects."
563   :group 'projectile
564   :type '(choice
565           (const :tag "Nothing" nil)
566           (const :tag "Remote files" file-remote-p)
567           function)
568   :package-version '(projectile . "0.13.0"))
569
570 (defcustom projectile-track-known-projects-automatically t
571   "Controls whether Projectile will automatically register known projects.
572
573 When set to nil you'll have always add projects explicitly with
574 `projectile-add-known-project'."
575   :group 'projectile
576   :type 'boolean
577   :package-version '(projectile . "1.0.0"))
578
579 (defcustom projectile-project-search-path nil
580   "List of folders where projectile is automatically going to look for projects.
581 You can think of something like $PATH, but for projects instead of executables.
582 Examples of such paths might be ~/projects, ~/work, etc."
583   :group 'projectile
584   :type 'list
585   :package-version '(projectile . "1.0.0"))
586
587 (defcustom projectile-git-command "git ls-files -zco --exclude-standard"
588   "Command used by projectile to get the files in a git project."
589   :group 'projectile
590   :type 'string)
591
592 (defcustom projectile-git-submodule-command "git submodule --quiet foreach 'echo $path' | tr '\\n' '\\0'"
593   "Command used by projectile to list submodules of a given git repository.
594 Set to nil to disable listing submodules contents."
595   :group 'projectile
596   :type 'string)
597
598 (defcustom projectile-git-ignored-command "git ls-files -zcoi --exclude-standard"
599   "Command used by projectile to get the ignored files in a git project."
600   :group 'projectile
601   :type 'string
602   :package-version '(projectile . "0.14.0"))
603
604 (defcustom projectile-hg-command "hg locate -f -0 -I ."
605   "Command used by projectile to get the files in a hg project."
606   :group 'projectile
607   :type 'string)
608
609 (defcustom projectile-fossil-command (concat "fossil ls | "
610                                              (when (string-equal system-type
611                                                                  "windows-nt")
612                                                "dos2unix | ")
613                                              "tr '\\n' '\\0'")
614   "Command used by projectile to get the files in a fossil project."
615   :group 'projectile
616   :type 'string)
617
618 (defcustom projectile-bzr-command "bzr ls -R --versioned -0"
619   "Command used by projectile to get the files in a bazaar project."
620   :group 'projectile
621   :type 'string)
622
623 (defcustom projectile-darcs-command "darcs show files -0 . "
624   "Command used by projectile to get the files in a darcs project."
625   :group 'projectile
626   :type 'string)
627
628 (defcustom projectile-svn-command "svn list -R . | grep -v '$/' | tr '\\n' '\\0'"
629   "Command used by projectile to get the files in a svn project."
630   :group 'projectile
631   :type 'string)
632
633 (defcustom projectile-generic-command "find . -type f -print0"
634   "Command used by projectile to get the files in a generic project."
635   :group 'projectile
636   :type 'string)
637
638 (defcustom projectile-vcs-dirty-state '("edited" "unregistered" "needs-update" "needs-merge" "unlocked-changes" "conflict")
639   "List of states checked by `projectile-browse-dirty-projects'.
640 Possible checked states are:
641 \"edited\", \"unregistered\", \"needs-update\", \"needs-merge\",
642 \"unlocked-changes\" and \"conflict\",
643 as defined in `vc.el'."
644   :group 'projectile
645   :type '(repeat (string))
646   :package-version '(projectile . "1.0.0"))
647
648 (defcustom projectile-other-file-alist
649   '( ;; handle C/C++ extensions
650     ("cpp" . ("h" "hpp" "ipp"))
651     ("ipp" . ("h" "hpp" "cpp"))
652     ("hpp" . ("h" "ipp" "cpp" "cc"))
653     ("cxx" . ("h" "hxx" "ixx"))
654     ("ixx" . ("h" "hxx" "cxx"))
655     ("hxx" . ("h" "ixx" "cxx"))
656     ("c"   . ("h"))
657     ("m"   . ("h"))
658     ("mm"  . ("h"))
659     ("h"   . ("c" "cc" "cpp" "ipp" "hpp" "cxx" "ixx" "hxx" "m" "mm"))
660     ("cc"  . ("h" "hh" "hpp"))
661     ("hh"  . ("cc"))
662
663     ;; vertex shader and fragment shader extensions in glsl
664     ("vert" . ("frag"))
665     ("frag" . ("vert"))
666
667     ;; handle files with no extension
668     (nil    . ("lock" "gpg"))
669     ("lock" . (""))
670     ("gpg"  . (""))
671     )
672   "Alist of extensions for switching to file with the same name,
673   using other extensions based on the extension of current
674   file."
675   :type 'alist)
676
677 (defcustom projectile-create-missing-test-files nil
678   "During toggling, if non-nil enables creating test files if not found.
679
680 When not-nil, every call to projectile-find-implementation-or-test-*
681 creates test files if not found on the file system.  Defaults to nil.
682 It assumes the test/ folder is at the same level as src/."
683   :group 'projectile
684   :type 'boolean)
685
686 (defcustom projectile-after-switch-project-hook nil
687   "Hooks run right after project is switched."
688   :group 'projectile
689   :type 'hook)
690
691 (defcustom projectile-before-switch-project-hook nil
692   "Hooks run when right before project is switched."
693   :group 'projectile
694   :type 'hook)
695
696 (defcustom projectile-current-project-on-switch 'remove
697   "Determines whether to display current project when switching projects.
698
699 When set to 'remove current project is not included, 'move-to-end
700 will display current project and the end of the list of known
701 projects, 'keep will leave the current project at the default
702 position."
703   :group 'projectile
704   :type '(radio
705           (const :tag "Remove" remove)
706           (const :tag "Move to end" move-to-end)
707           (const :tag "Keep" keep)))
708
709
710 ;;; Version information
711
712 ;;;###autoload
713 (defun projectile-version (&optional show-version)
714   "Get the Projectile version as string.
715
716 If called interactively or if SHOW-VERSION is non-nil, show the
717 version in the echo area and the messages buffer.
718
719 The returned string includes both, the version from package.el
720 and the library version, if both a present and different.
721
722 If the version number could not be determined, signal an error,
723 if called interactively, or if SHOW-VERSION is non-nil, otherwise
724 just return nil."
725   (interactive (list t))
726   (if (require 'pkg-info nil t)
727       (let ((version (pkg-info-version-info 'projectile)))
728         (when show-version
729           (message "Projectile %s" version))
730         version)
731     (error "Cannot determine version without package pkg-info")))
732
733 ;;; Misc utility functions
734 (defun projectile-difference (list1 list2)
735   (cl-remove-if
736    (lambda (x) (member x list2))
737    list1))
738
739 (defun projectile-unixy-system-p ()
740   "Check to see if unixy text utilities are installed."
741   (cl-every
742    (lambda (x) (executable-find x))
743    '("grep" "cut" "uniq")))
744
745 (defun projectile-symbol-or-selection-at-point ()
746   "Get the symbol or selected text at point."
747   (if (use-region-p)
748       (buffer-substring-no-properties (region-beginning) (region-end))
749     (projectile-symbol-at-point)))
750
751 (defun projectile-symbol-at-point ()
752   "Get the symbol at point and strip its properties."
753   (substring-no-properties (or (thing-at-point 'symbol) "")))
754
755
756
757 ;;; Serialization
758 (defun projectile-serialize (data filename)
759   "Serialize DATA to FILENAME.
760
761 The saved data can be restored with `projectile-unserialize'."
762   (when (file-writable-p filename)
763     (with-temp-file filename
764       (insert (let (print-length) (prin1-to-string data))))))
765
766 (defun projectile-unserialize (filename)
767   "Read data serialized by `projectile-serialize' from FILENAME."
768   (with-demoted-errors
769       "Error during file deserialization: %S"
770     (when (file-exists-p filename)
771       (with-temp-buffer
772         (insert-file-contents filename)
773         ;; this will blow up if the contents of the file aren't
774         ;; lisp data structures
775         (read (buffer-string))))))
776
777
778 ;;; Caching
779 (defvar projectile-file-exists-cache
780   (make-hash-table :test 'equal)
781   "Cached `projectile-file-exists-p' results.")
782
783 (defvar projectile-file-exists-cache-timer nil
784   "Timer for scheduling`projectile-file-exists-cache-cleanup'.")
785
786 (defun projectile-file-exists-cache-cleanup ()
787   "Removed timed out cache entries and reschedules or remove the
788 timer if no more items are in the cache."
789   (let ((now (current-time)))
790     (maphash (lambda (key value)
791                (if (time-less-p (cdr value) now)
792                    (remhash key projectile-file-exists-cache)))
793              projectile-file-exists-cache)
794     (setq projectile-file-exists-cache-timer
795           (if (> (hash-table-count projectile-file-exists-cache) 0)
796               (run-with-timer 10 nil 'projectile-file-exists-cache-cleanup)))))
797
798 (defun projectile-file-exists-p (filename)
799   "Return t if file FILENAME exist.
800 A wrapper around `file-exists-p' with additional caching support."
801   (let* ((file-remote (file-remote-p filename))
802          (expire-seconds
803           (if file-remote
804               (and projectile-file-exists-remote-cache-expire
805                    (> projectile-file-exists-remote-cache-expire 0)
806                    projectile-file-exists-remote-cache-expire)
807             (and projectile-file-exists-local-cache-expire
808                  (> projectile-file-exists-local-cache-expire 0)
809                  projectile-file-exists-local-cache-expire)))
810          (remote-file-name-inhibit-cache (if expire-seconds
811                                              expire-seconds
812                                            remote-file-name-inhibit-cache)))
813     (if (not expire-seconds)
814         (file-exists-p filename)
815       (let* ((current-time (current-time))
816              (cached (gethash filename projectile-file-exists-cache))
817              (cached-value (if cached (car cached)))
818              (cached-expire (if cached (cdr cached)))
819              (cached-expired (if cached (time-less-p cached-expire current-time) t))
820              (value (or (and (not cached-expired) cached-value)
821                         (if (file-exists-p filename) 'found 'notfound))))
822         (when (or (not cached) cached-expired)
823           (puthash filename
824                    (cons value (time-add current-time (seconds-to-time expire-seconds)))
825                    projectile-file-exists-cache))
826         (unless projectile-file-exists-cache-timer
827           (setq projectile-file-exists-cache-timer
828                 (run-with-timer 10 nil 'projectile-file-exists-cache-cleanup)))
829         (equal value 'found)))))
830
831 ;;;###autoload
832 (defun projectile-invalidate-cache (prompt)
833   "Remove the current project's files from `projectile-projects-cache'.
834
835 With a prefix argument PROMPT prompts for the name of the project whose cache
836 to invalidate."
837   (interactive "P")
838   (let ((project-root
839          (if prompt
840              (completing-read "Remove cache for: "
841                               (hash-table-keys projectile-projects-cache))
842            (projectile-ensure-project (projectile-project-root)))))
843     (setq projectile-project-root-cache (make-hash-table :test 'equal))
844     (remhash project-root projectile-project-type-cache)
845     (remhash project-root projectile-projects-cache)
846     (remhash project-root projectile-projects-cache-time)
847     (projectile-serialize-cache)
848     (when projectile-verbose
849       (message "Invalidated Projectile cache for %s."
850                (propertize project-root 'face 'font-lock-keyword-face))))
851   (when (fboundp 'recentf-cleanup)
852     (recentf-cleanup)))
853
854 (defun projectile-time-seconds ()
855   "Return the number of seconds since the unix epoch."
856   (cl-destructuring-bind (high low _usec _psec) (current-time)
857     (+ (lsh high 16) low)))
858
859 (defun projectile-cache-project (project files)
860   "Cache PROJECTs FILES.
861 The cache is created both in memory and on the hard drive."
862   (when projectile-enable-caching
863     (puthash project files projectile-projects-cache)
864     (puthash project (projectile-time-seconds) projectile-projects-cache-time)
865     (projectile-serialize-cache)))
866
867 ;;;###autoload
868 (defun projectile-purge-file-from-cache (file)
869   "Purge FILE from the cache of the current project."
870   (interactive
871    (list (projectile-completing-read
872           "Remove file from cache: "
873           (projectile-current-project-files))))
874   (let* ((project-root (projectile-project-root))
875          (project-cache (gethash project-root projectile-projects-cache)))
876     (if (projectile-file-cached-p file project-root)
877         (progn
878           (puthash project-root (remove file project-cache) projectile-projects-cache)
879           (projectile-serialize-cache)
880           (when projectile-verbose
881             (message "%s removed from cache" file)))
882       (error "%s is not in the cache" file))))
883
884 ;;;###autoload
885 (defun projectile-purge-dir-from-cache (dir)
886   "Purge DIR from the cache of the current project."
887   (interactive
888    (list (projectile-completing-read
889           "Remove directory from cache: "
890           (projectile-current-project-dirs))))
891   (let* ((project-root (projectile-project-root))
892          (project-cache (gethash project-root projectile-projects-cache)))
893     (puthash project-root
894              (cl-remove-if (lambda (str) (string-prefix-p dir str)) project-cache)
895              projectile-projects-cache)))
896
897 (defun projectile-file-cached-p (file project)
898   "Check if FILE is already in PROJECT cache."
899   (member file (gethash project projectile-projects-cache)))
900
901 ;;;###autoload
902 (defun projectile-cache-current-file ()
903   "Add the currently visited file to the cache."
904   (interactive)
905   (let ((current-project (projectile-project-root)))
906     (when (and (buffer-file-name) (gethash (projectile-project-root) projectile-projects-cache))
907       (let* ((abs-current-file (file-truename (buffer-file-name)))
908              (current-file (file-relative-name abs-current-file current-project)))
909         (unless (or (projectile-file-cached-p current-file current-project)
910                     (projectile-ignored-directory-p (file-name-directory abs-current-file))
911                     (projectile-ignored-file-p abs-current-file))
912           (puthash current-project
913                    (cons current-file (gethash current-project projectile-projects-cache))
914                    projectile-projects-cache)
915           (projectile-serialize-cache)
916           (message "File %s added to project %s cache."
917                    (propertize current-file 'face 'font-lock-keyword-face)
918                    (propertize current-project 'face 'font-lock-keyword-face)))))))
919
920 ;; cache opened files automatically to reduce the need for cache invalidation
921 (defun projectile-cache-files-find-file-hook ()
922   "Function for caching files with `find-file-hook'."
923   (let ((project-root (projectile-project-p)))
924     (when (and projectile-enable-caching
925                project-root
926                (not (projectile-ignored-project-p project-root)))
927       (projectile-cache-current-file))))
928
929 (defun projectile-track-known-projects-find-file-hook ()
930   "Function for caching projects with `find-file-hook'."
931   (when (and projectile-track-known-projects-automatically (projectile-project-p))
932     (projectile-add-known-project (projectile-project-root))))
933
934 (defun projectile-maybe-invalidate-cache (force)
935   "Invalidate if FORCE or project's dirconfig newer than cache."
936   (when (or force (file-newer-than-file-p (projectile-dirconfig-file)
937                                           projectile-cache-file))
938     (projectile-invalidate-cache nil)))
939
940 ;;;###autoload
941 (defun projectile-discover-projects-in-directory (directory)
942   "Discover any projects in DIRECTORY and add them to the projectile cache.
943 This function is not recursive and only adds projects with roots
944 at the top level of DIRECTORY."
945   (interactive
946    (list (read-directory-name "Starting directory: ")))
947   (let ((subdirs (directory-files directory t)))
948     (mapcar
949      (lambda (dir)
950        (when (and (file-directory-p dir)
951                   (not (member (file-name-nondirectory dir) '(".." "."))))
952          (when (projectile-project-p dir)
953            (projectile-add-known-project dir))))
954      subdirs)))
955
956 ;;;###autoload
957 (defun projectile-discover-projects-in-search-path ()
958   "Discover projects in `projectile-project-search-path'.
959 Invoked automatically when `projectile-mode' is enabled."
960   (interactive)
961   (mapcar #'projectile-discover-projects-in-directory projectile-project-search-path))
962
963
964 (defadvice delete-file (before purge-from-projectile-cache (filename &optional trash))
965   (if (and projectile-enable-caching (projectile-project-p))
966       (let* ((project-root (projectile-project-root))
967              (true-filename (file-truename filename))
968              (relative-filename (file-relative-name true-filename project-root)))
969         (if (projectile-file-cached-p relative-filename project-root)
970             (projectile-purge-file-from-cache relative-filename)))))
971
972
973 ;;; Project root related utilities
974 (defun projectile-parent (path)
975   "Return the parent directory of PATH.
976 PATH may be a file or directory and directory paths may end with a slash."
977   (directory-file-name (file-name-directory (directory-file-name (expand-file-name path)))))
978
979 (defun projectile-locate-dominating-file (file name)
980   "Look up the directory hierarchy from FILE for a directory containing NAME.
981 Stop at the first parent directory containing a file NAME,
982 and return the directory.  Return nil if not found.
983 Instead of a string, NAME can also be a predicate taking one argument
984 \(a directory) and returning a non-nil value if that directory is the one for
985 which we're looking."
986   ;; copied from files.el (stripped comments) emacs-24 bzr branch 2014-03-28 10:20
987   (setq file (abbreviate-file-name file))
988   (let ((root nil)
989         try)
990     (while (not (or root
991                     (null file)
992                     (string-match locate-dominating-stop-dir-regexp file)))
993       (setq try (if (stringp name)
994                     (projectile-file-exists-p (expand-file-name name file))
995                   (funcall name file)))
996       (cond (try (setq root file))
997             ((equal file (setq file (file-name-directory
998                                      (directory-file-name file))))
999              (setq file nil))))
1000     (and root (expand-file-name (file-name-as-directory root)))))
1001
1002 (defvar-local projectile-project-root nil
1003   "Defines a custom Projectile project root.
1004 This is intended to be used as a file local variable.")
1005
1006 (defun projectile-root-local (_dir)
1007   "A simple wrapper around `projectile-project-root'."
1008   projectile-project-root)
1009
1010 (defun projectile-root-top-down (dir &optional list)
1011   "Identify a project root in DIR by top-down search for files in LIST.
1012 If LIST is nil, use `projectile-project-root-files' instead.
1013 Return the first (topmost) matched directory or nil if not found."
1014   (projectile-locate-dominating-file
1015    dir
1016    (lambda (dir)
1017      (cl-find-if (lambda (f) (projectile-file-exists-p (expand-file-name f dir)))
1018                  (or list projectile-project-root-files)))))
1019
1020 (defun projectile-root-bottom-up (dir &optional list)
1021   "Identify a project root in DIR by bottom-up search for files in LIST.
1022 If LIST is nil, use `projectile-project-root-files-bottom-up' instead.
1023 Return the first (bottommost) matched directory or nil if not found."
1024   (cl-some (lambda (name) (projectile-locate-dominating-file dir name))
1025            (or list projectile-project-root-files-bottom-up)))
1026
1027 (defun projectile-root-top-down-recurring (dir &optional list)
1028   "Identify a project root in DIR by recurring top-down search for files in LIST.
1029 If LIST is nil, use `projectile-project-root-files-top-down-recurring'
1030 instead.  Return the last (bottommost) matched directory in the
1031 topmost sequence of matched directories.  Nil otherwise."
1032   (cl-some
1033    (lambda (f)
1034      (projectile-locate-dominating-file
1035       dir
1036       (lambda (dir)
1037         (and (projectile-file-exists-p (expand-file-name f dir))
1038              (or (string-match locate-dominating-stop-dir-regexp (projectile-parent dir))
1039                  (not (projectile-file-exists-p (expand-file-name f (projectile-parent dir)))))))))
1040    (or list projectile-project-root-files-top-down-recurring)))
1041
1042 (defun projectile-project-root (&optional dir)
1043   "Retrieves the root directory of a project if available.
1044 If DIR is not supplied its set to the current directory by default."
1045   ;; the cached value will be 'none in the case of no project root (this is to
1046   ;; ensure it is not reevaluated each time when not inside a project) so use
1047   ;; cl-subst to replace this 'none value with nil so a nil value is used
1048   ;; instead
1049   (let ((dir (or dir default-directory)))
1050     (cl-subst nil 'none
1051               ;; The `is-local' and `is-connected' variables are
1052               ;; used to fix the behavior where Emacs hangs
1053               ;; because of Projectile when you open a file over
1054               ;; TRAMP. It basically prevents Projectile from
1055               ;; trying to find information about files for which
1056               ;; it's not possible to get that information right
1057               ;; now.
1058               (or (let ((is-local (not (file-remote-p dir)))      ;; `true' if the file is local
1059                         (is-connected (file-remote-p dir nil t))) ;; `true' if the file is remote AND we are connected to the remote
1060                     (when (or is-local is-connected)
1061                       (cl-some
1062                        (lambda (func)
1063                          (let* ((cache-key (format "%s-%s" func dir))
1064                                 (cache-value (gethash cache-key projectile-project-root-cache)))
1065                            (if (and cache-value (file-exists-p cache-value))
1066                                cache-value
1067                              (let ((value (funcall func (file-truename dir))))
1068                                (puthash cache-key value projectile-project-root-cache)
1069                                value))))
1070                        projectile-project-root-files-functions)))
1071                   ;; set cached to none so is non-nil so we don't try
1072                   ;; and look it up again
1073                   'none))))
1074
1075 (defun projectile-ensure-project (dir)
1076   "Ensure that DIR is non-nil.
1077 Useful for commands that expect the presence of a project.
1078 Controlled by `projectile-require-project-root'."
1079   (if dir
1080       dir
1081     (cond
1082      ((eq projectile-require-project-root 'prompt) (projectile-completing-read
1083                                                     "Switch to project: " projectile-known-projects))
1084      (projectile-require-project-root (error "Projectile can't find a project definition in %s" dir))
1085      (t default-directory))))
1086
1087 (defun projectile-project-p (&optional dir)
1088   "Check if DIR is a project.
1089 Defaults to the current directory if not provided
1090 explicitly."
1091   (projectile-project-root (or dir default-directory)))
1092
1093 (defun projectile-default-project-name (project-root)
1094   "Default function used create project name to be displayed based on the value of PROJECT-ROOT."
1095   (file-name-nondirectory (directory-file-name project-root)))
1096
1097 (defun projectile-project-name (&optional project)
1098   "Return project name.
1099 If PROJECT is not specified acts on the current project."
1100   (or projectile-project-name
1101       (let ((project-root (or project (projectile-project-root))))
1102         (if project-root
1103             (funcall projectile-project-name-function project-root)
1104           "-"))))
1105
1106
1107 ;;; Project indexing
1108 (defun projectile-get-project-directories (project-dir)
1109   "Get the list of PROJECT-DIR directories that are of interest to the user."
1110   (mapcar (lambda (subdir) (concat project-dir subdir))
1111           (or (nth 0 (projectile-parse-dirconfig-file)) '(""))))
1112
1113 (defun projectile--directory-p (directory)
1114   "Checks if DIRECTORY is a string designating a valid directory."
1115   (and (stringp directory) (file-directory-p directory)))
1116
1117 (defun projectile-dir-files (directory)
1118   "List the files in DIRECTORY and in its sub-directories.
1119 Files are returned as relative paths to DIRECTORY."
1120   (unless (projectile--directory-p directory)
1121     (error "Directory %S does not exist" directory))
1122   ;; check for a cache hit first if caching is enabled
1123   (let ((files-list (and projectile-enable-caching
1124                          (gethash directory projectile-projects-cache))))
1125     ;; cache disabled or cache miss
1126     (or files-list
1127         (let ((vcs (projectile-project-vcs directory)))
1128           (pcase projectile-indexing-method
1129            ('native (projectile-dir-files-native directory))
1130            ;; use external tools to get the project files
1131            ('hybrid (projectile-adjust-files directory vcs (projectile-dir-files-alien directory)))
1132            ('alien (projectile-dir-files-alien directory))
1133            (_ (user-error "Unsupported indexing method `%S'" projectile-indexing-method)))))))
1134
1135 ;;; Native Project Indexing
1136 ;;
1137 ;; This corresponds to `projectile-indexing-method' being set to native.
1138 (defun projectile-dir-files-native (directory)
1139   "Get the files for ROOT under DIRECTORY using just Emacs Lisp."
1140   (let ((progress-reporter
1141          (make-progress-reporter
1142           (format "Projectile is indexing %s"
1143                   (propertize directory 'face 'font-lock-keyword-face)))))
1144     ;; we need the files with paths relative to the project root
1145     (mapcar (lambda (file) (file-relative-name file directory))
1146             (projectile-index-directory directory (projectile-filtering-patterns)
1147                                         progress-reporter))))
1148
1149 (defun projectile-index-directory (directory patterns progress-reporter)
1150   "Index DIRECTORY taking into account PATTERNS.
1151 The function calls itself recursively until all sub-directories
1152 have been indexed.  The PROGRESS-REPORTER is updated while the
1153 function is executing."
1154   (apply 'append
1155          (mapcar
1156           (lambda (f)
1157             (unless (or (and patterns (projectile-ignored-rel-p f directory patterns))
1158                         (member (file-name-nondirectory (directory-file-name f))
1159                                 '("." ".." ".svn" ".cvs")))
1160               (progress-reporter-update progress-reporter)
1161               (if (file-directory-p f)
1162                   (unless (projectile-ignored-directory-p
1163                            (file-name-as-directory f))
1164                     (projectile-index-directory f patterns progress-reporter))
1165                 (unless (projectile-ignored-file-p f)
1166                   (list f)))))
1167           (directory-files directory t))))
1168
1169 ;;; Alien Project Indexing
1170 ;;
1171 ;; This corresponds to `projectile-indexing-method' being set to hybrid or alien.
1172 ;; The only difference between the two methods is that alien doesn't do
1173 ;; any post-processing of the files obtained via the external command.
1174 (defun projectile-dir-files-alien (directory)
1175   "Get the files for DIRECTORY using external tools."
1176   (let ((vcs (projectile-project-vcs directory)))
1177     (cond
1178     ((eq vcs 'git)
1179      (nconc (projectile-files-via-ext-command directory (projectile-get-ext-command vcs))
1180             (projectile-get-sub-projects-files directory vcs)))
1181     (t (projectile-files-via-ext-command directory (projectile-get-ext-command vcs))))))
1182
1183 (define-obsolete-function-alias 'projectile-dir-files-external 'projectile-dir-files-alien "1.1")
1184 (define-obsolete-function-alias 'projectile-get-repo-files 'projectile-dir-files-alien "1.1")
1185
1186 (defun projectile-get-ext-command (vcs)
1187   "Determine which external command to invoke based on the project's VCS.
1188 Fallback to a generic command when not in a VCS-controlled project."
1189   (pcase vcs
1190    ('git projectile-git-command)
1191    ('hg projectile-hg-command)
1192    ('fossil projectile-fossil-command)
1193    ('bzr projectile-bzr-command)
1194    ('darcs projectile-darcs-command)
1195    ('svn projectile-svn-command)
1196    (_ projectile-generic-command)))
1197
1198 (defun projectile-get-sub-projects-command (vcs)
1199   "Get the sub-projects command for VCS.
1200 Currently that's supported just for Git (sub-projects being Git
1201 sub-modules there)."
1202   (pcase vcs
1203    ('git projectile-git-submodule-command)
1204    (_ "")))
1205
1206 (defun projectile-get-ext-ignored-command (vcs)
1207   "Determine which external command to invoke based on the project's VCS."
1208   (pcase vcs
1209    ('git projectile-git-ignored-command)
1210    ;; TODO: Add support for other VCS
1211    (_ nil)))
1212
1213 (defun projectile-flatten (lst)
1214   "Take a nested list LST and return its contents as a single, flat list."
1215   (if (and (listp lst) (listp (cdr lst)))
1216       (cl-mapcan 'projectile-flatten lst)
1217     (list lst)))
1218
1219 (defun projectile-get-all-sub-projects (project)
1220   "Get all sub-projects for a given project.
1221
1222 PROJECT is base directory to start search recursively."
1223   (let ((submodules (projectile-get-immediate-sub-projects project)))
1224     (cond
1225      ((null submodules)
1226       nil)
1227      (t
1228       (nconc submodules (projectile-flatten
1229                          ;; recursively get sub-projects of each sub-project
1230                          (mapcar (lambda (s)
1231                                    (projectile-get-all-sub-projects s)) submodules)))))))
1232
1233 (defun projectile-get-immediate-sub-projects (path)
1234   "Get immediate sub-projects for a given project without recursing.
1235
1236 PATH is the vcs root or project root from which to start
1237 searching, and should end with an appropriate path delimiter, such as
1238 '/' or a '\\'.
1239
1240 If the vcs get-sub-projects query returns results outside of path,
1241 they are excluded from the results of this function."
1242   (let* ((vcs (projectile-project-vcs path))
1243          ;; search for sub-projects under current project `project'
1244          (submodules (mapcar
1245                       (lambda (s)
1246                         (file-name-as-directory (expand-file-name s path)))
1247                       (projectile-files-via-ext-command path (projectile-get-sub-projects-command vcs))))
1248          (project-child-folder-regex
1249           (concat "\\`"
1250                   (regexp-quote path))))
1251
1252     ;; If project root is inside of an VCS folder, but not actually an
1253     ;; VCS root itself, submodules external to the project will be
1254     ;; included in the VCS get sub-projects result. Let's remove them.
1255     (cl-remove-if-not
1256      (lambda (submodule)
1257        (string-match-p project-child-folder-regex
1258                        submodule))
1259      submodules)))
1260
1261 (defun projectile-get-sub-projects-files (project-root vcs)
1262   "Get files from sub-projects for PROJECT-ROOT recursively."
1263   (projectile-flatten
1264    (mapcar (lambda (sub-project)
1265              (mapcar (lambda (file)
1266                        (concat sub-project file))
1267                      ;; TODO: Seems we forgot git hardcoded here
1268                      (projectile-files-via-ext-command sub-project projectile-git-command)))
1269            (projectile-get-all-sub-projects project-root))))
1270
1271 (defun projectile-get-repo-ignored-files (project vcs)
1272   "Get a list of the files ignored in the PROJECT using VCS."
1273   (let ((cmd (projectile-get-ext-ignored-command vcs)))
1274     (when cmd
1275       (projectile-files-via-ext-command project cmd))))
1276
1277 (defun projectile-get-repo-ignored-directory (project dir vcs)
1278   "Get a list of the files ignored in the PROJECT in the directory DIR.
1279 VCS is the VCS of the project."
1280   (let ((cmd (projectile-get-ext-ignored-command vcs)))
1281     (when cmd
1282       (projectile-files-via-ext-command project (concat cmd " " dir)))))
1283
1284 (defun projectile-files-via-ext-command (root command)
1285   "Get a list of relative file names in the project ROOT by executing COMMAND.
1286
1287 If `command' is nil or an empty string, return nil.
1288 This allows commands to be disabled."
1289   (when (stringp command)
1290     (let ((default-directory root))
1291       (split-string (shell-command-to-string command) "\0" t))))
1292
1293 (defun projectile-adjust-files (project vcs files)
1294   "First remove ignored files from FILES, then add back unignored files."
1295   (projectile-add-unignored project vcs (projectile-remove-ignored files)))
1296
1297 (defun projectile-remove-ignored (files)
1298   "Remove ignored files and folders from FILES.
1299
1300 If ignored directory prefixed with '*', then ignore all
1301 directories/subdirectories with matching filename,
1302 otherwise operates relative to project root."
1303   (let ((ignored-files (projectile-ignored-files-rel))
1304         (ignored-dirs (projectile-ignored-directories-rel)))
1305     (cl-remove-if
1306      (lambda (file)
1307        (or (cl-some
1308             (lambda (f)
1309               (string= f (file-name-nondirectory file)))
1310             ignored-files)
1311            (cl-some
1312             (lambda (dir)
1313               ;; if the directory is prefixed with '*' then ignore all directories matching that name
1314               (if (string-prefix-p "*" dir)
1315                   ;; remove '*' and trailing slash from ignored directory name
1316                   (let ((d (substring dir 1 (if (equal (substring dir -1) "/") -1 nil))))
1317                     (cl-some
1318                      (lambda (p)
1319                        (string= d p))
1320                      ;; split path by '/', remove empty strings, and check if any subdirs match name 'd'
1321                      (delete "" (split-string (or (file-name-directory file) "") "/"))))
1322                 (string-prefix-p dir file)))
1323             ignored-dirs)
1324            (cl-some
1325             (lambda (suf)
1326               (string-suffix-p suf file t))
1327             projectile-globally-ignored-file-suffixes)))
1328      files)))
1329
1330 (defun projectile-keep-ignored-files (project vcs files)
1331   "Filter FILES to retain only those that are ignored."
1332   (when files
1333     (cl-remove-if-not
1334      (lambda (file)
1335        (cl-some (lambda (f) (string-prefix-p f file)) files))
1336      (projectile-get-repo-ignored-files project vcs))))
1337
1338 (defun projectile-keep-ignored-directories (project vcs directories)
1339   "Get ignored files within each of DIRECTORIES."
1340   (when directories
1341     (let (result)
1342       (dolist (dir directories result)
1343         (setq result (append result
1344                              (projectile-get-repo-ignored-directory project vcs dir))))
1345       result)))
1346
1347 (defun projectile-add-unignored (project vcs files)
1348   "This adds unignored files to FILES.
1349
1350 Useful because the VCS may not return ignored files at all.  In
1351 this case unignored files will be absent from FILES."
1352   (let ((unignored-files (projectile-keep-ignored-files
1353                           project
1354                           vcs
1355                           (projectile-unignored-files-rel)))
1356         (unignored-paths (projectile-remove-ignored
1357                           (projectile-keep-ignored-directories
1358                            project
1359                            vcs
1360                            (projectile-unignored-directories-rel)))))
1361     (append files unignored-files unignored-paths)))
1362
1363 (defun projectile-buffers-with-file (buffers)
1364   "Return only those BUFFERS backed by files."
1365   (cl-remove-if-not (lambda (b) (buffer-file-name b)) buffers))
1366
1367 (defun projectile-buffers-with-file-or-process (buffers)
1368   "Return only those BUFFERS backed by files or processes."
1369   (cl-remove-if-not (lambda (b) (or (buffer-file-name b)
1370                                     (get-buffer-process b))) buffers))
1371
1372 (defun projectile-project-buffers (&optional project)
1373   "Get a list of a project's buffers.
1374 If PROJECT is not specified the command acts on the current project."
1375   (let* ((project-root (or project (projectile-project-root)))
1376          (all-buffers (cl-remove-if-not
1377                        (lambda (buffer)
1378                          (projectile-project-buffer-p buffer project-root))
1379                        (buffer-list))))
1380     (if projectile-buffers-filter-function
1381         (funcall projectile-buffers-filter-function all-buffers)
1382       all-buffers)))
1383
1384 (defun projectile-process-current-project-buffers (action)
1385   "Process the current project's buffers using ACTION."
1386   (let ((project-buffers (projectile-project-buffers)))
1387     (dolist (buffer project-buffers)
1388       (funcall action buffer))))
1389
1390 (defun projectile-project-buffer-files (&optional project)
1391   "Get a list of a project's buffer files.
1392 If PROJECT is not specified the command acts on the current project."
1393   (let ((project-root (or project (projectile-project-root))))
1394     (mapcar
1395      (lambda (buffer)
1396        (file-relative-name
1397         (buffer-file-name buffer)
1398         project-root))
1399      (projectile-buffers-with-file
1400       (projectile-project-buffers project)))))
1401
1402 (defun projectile-project-buffer-p (buffer project-root)
1403   "Check if BUFFER is under PROJECT-ROOT."
1404   (with-current-buffer buffer
1405     (and (not (string-prefix-p " " (buffer-name buffer)))
1406          (not (projectile-ignored-buffer-p buffer))
1407          default-directory
1408          (string-equal (file-remote-p default-directory)
1409                        (file-remote-p project-root))
1410          (not (string-match-p "^http\\(s\\)?://" default-directory))
1411          (string-prefix-p project-root (file-truename default-directory) (eq system-type 'windows-nt)))))
1412
1413 (defun projectile-ignored-buffer-p (buffer)
1414   "Check if BUFFER should be ignored.
1415
1416 Regular expressions can be use."
1417   (or
1418    (with-current-buffer buffer
1419      (cl-some
1420       (lambda (name)
1421         (string-match-p name (buffer-name)))
1422       projectile-globally-ignored-buffers))
1423    (with-current-buffer buffer
1424      (cl-some
1425       (lambda (mode)
1426         (string-match-p (concat "^" mode "$")
1427                         (symbol-name major-mode)))
1428       projectile-globally-ignored-modes))))
1429
1430 (defun projectile-recently-active-files ()
1431   "Get list of recently active files.
1432
1433 Files are ordered by recently active buffers, and then recently
1434 opened through use of recentf."
1435   (let ((project-buffer-files (projectile-project-buffer-files)))
1436     (append project-buffer-files
1437             (projectile-difference
1438              (projectile-recentf-files)
1439              project-buffer-files))))
1440
1441 (defun projectile-project-buffer-names ()
1442   "Get a list of project buffer names."
1443   (mapcar #'buffer-name (projectile-project-buffers)))
1444
1445 (defun projectile-prepend-project-name (string)
1446   "Prepend the current project's name to STRING."
1447   (format "[%s] %s" (projectile-project-name) string))
1448
1449 (defun projectile-read-buffer-to-switch (prompt)
1450   "Read the name of a buffer to switch to, prompting with PROMPT.
1451
1452 This function excludes the current buffer from the offered
1453 choices."
1454   (projectile-completing-read
1455    prompt
1456    (delete (buffer-name (current-buffer))
1457            (projectile-project-buffer-names))))
1458
1459 ;;;###autoload
1460 (defun projectile-switch-to-buffer ()
1461   "Switch to a project buffer."
1462   (interactive)
1463   (switch-to-buffer
1464    (projectile-read-buffer-to-switch "Switch to buffer: ")))
1465
1466 ;;;###autoload
1467 (defun projectile-switch-to-buffer-other-window ()
1468   "Switch to a project buffer and show it in another window."
1469   (interactive)
1470   (switch-to-buffer-other-window
1471    (projectile-read-buffer-to-switch "Switch to buffer: ")))
1472
1473 ;;;###autoload
1474 (defun projectile-switch-to-buffer-other-frame ()
1475   "Switch to a project buffer and show it in another window."
1476   (interactive)
1477   (switch-to-buffer-other-frame
1478    (projectile-read-buffer-to-switch "Switch to buffer: ")))
1479
1480 ;;;###autoload
1481 (defun projectile-display-buffer ()
1482   "Display a project buffer in another window without selecting it."
1483   (interactive)
1484   (display-buffer
1485    (projectile-completing-read
1486     "Display buffer: "
1487     (projectile-project-buffer-names))))
1488
1489 ;;;###autoload
1490 (defun projectile-project-buffers-other-buffer ()
1491   "Switch to the most recently selected buffer project buffer.
1492 Only buffers not visible in windows are returned."
1493   (interactive)
1494   (switch-to-buffer (car (projectile-project-buffers-non-visible))) nil t)
1495
1496 (defun projectile-project-buffers-non-visible ()
1497   "Get a list of non visible project buffers."
1498   (cl-remove-if-not
1499    (lambda (buffer)
1500      (not (get-buffer-window buffer 'visible)))
1501    (projectile-project-buffers)))
1502
1503 ;;;###autoload
1504 (defun projectile-multi-occur (&optional nlines)
1505   "Do a `multi-occur' in the project's buffers.
1506 With a prefix argument, show NLINES of context."
1507   (interactive "P")
1508   (let ((project (projectile-ensure-project (projectile-project-root))))
1509     (multi-occur (projectile-project-buffers project)
1510                  (car (occur-read-primary-args))
1511                  nlines)))
1512
1513 (defun projectile-normalise-paths (patterns)
1514   "Remove leading `/' from the elements of PATTERNS."
1515   (delq nil (mapcar (lambda (pat) (and (string-prefix-p "/" pat)
1516                                        ;; remove the leading /
1517                                        (substring pat 1)))
1518                     patterns)))
1519
1520 (defun projectile-expand-paths (paths)
1521   "Expand the elements of PATHS.
1522
1523 Elements containing wildcards are expanded and spliced into the
1524 resulting paths.  The returned PATHS are absolute, based on the
1525 projectile project root."
1526   (let ((default-directory (projectile-project-root)))
1527     (projectile-flatten (mapcar
1528                          (lambda (pattern)
1529                            (or (file-expand-wildcards pattern t)
1530                                (projectile-expand-root pattern)))
1531                          paths))))
1532
1533 (defun projectile-normalise-patterns (patterns)
1534   "Remove paths from PATTERNS."
1535   (cl-remove-if (lambda (pat) (string-prefix-p "/" pat)) patterns))
1536
1537 (defun projectile-make-relative-to-root (files)
1538   "Make FILES relative to the project root."
1539   (let ((project-root (projectile-project-root)))
1540     (mapcar (lambda (f) (file-relative-name f project-root)) files)))
1541
1542 (defun projectile-ignored-directory-p (directory)
1543   "Check if DIRECTORY should be ignored.
1544
1545 Regular expressions can be used."
1546   (cl-some
1547    (lambda (name)
1548      (string-match-p name directory))
1549    (projectile-ignored-directories)))
1550
1551 (defun projectile-ignored-file-p (file)
1552   "Check if FILE should be ignored.
1553
1554 Regular expressions can be used."
1555   (cl-some
1556    (lambda (name)
1557      (string-match-p name file))
1558    (projectile-ignored-files)))
1559
1560 (defun projectile-check-pattern-p (file pattern)
1561   "Check if FILE meets PATTERN."
1562   (or (string-suffix-p (directory-file-name pattern)
1563                       (directory-file-name file))
1564      (member file (file-expand-wildcards pattern t))))
1565
1566 (defun projectile-ignored-rel-p (file directory patterns)
1567   "Check if FILE should be ignored relative to DIRECTORY
1568 according to PATTERNS: (ignored . unignored)"
1569   (let ((default-directory directory))
1570     (and (cl-some
1571           (lambda (pat) (projectile-check-pattern-p file pat))
1572           (car patterns))
1573          (cl-notany
1574           (lambda (pat) (projectile-check-pattern-p file pat))
1575           (cdr patterns)))))
1576
1577 (defun projectile-ignored-files ()
1578   "Return list of ignored files."
1579   (projectile-difference
1580    (mapcar
1581     #'projectile-expand-root
1582     (append
1583      projectile-globally-ignored-files
1584      (projectile-project-ignored-files)))
1585    (projectile-unignored-files)))
1586
1587 (defun projectile-ignored-directories ()
1588   "Return list of ignored directories."
1589   (projectile-difference
1590    (mapcar
1591     #'file-name-as-directory
1592     (mapcar
1593      #'projectile-expand-root
1594      (append
1595       projectile-globally-ignored-directories
1596       (projectile-project-ignored-directories))))
1597    (projectile-unignored-directories)))
1598
1599 (defun projectile-ignored-directories-rel ()
1600   "Return list of ignored directories, relative to the root."
1601   (projectile-make-relative-to-root (projectile-ignored-directories)))
1602
1603 (defun projectile-ignored-files-rel ()
1604   "Return list of ignored files, relative to the root."
1605   (projectile-make-relative-to-root (projectile-ignored-files)))
1606
1607 (defun projectile-project-ignored-files ()
1608   "Return list of project ignored files.
1609 Unignored files are not included."
1610   (cl-remove-if 'file-directory-p (projectile-project-ignored)))
1611
1612 (defun projectile-project-ignored-directories ()
1613   "Return list of project ignored directories.
1614 Unignored directories are not included."
1615   (cl-remove-if-not 'file-directory-p (projectile-project-ignored)))
1616
1617 (defun projectile-paths-to-ignore ()
1618   "Return a list of ignored project paths."
1619   (projectile-normalise-paths (nth 1 (projectile-parse-dirconfig-file))))
1620
1621 (defun projectile-patterns-to-ignore ()
1622   "Return a list of relative file patterns."
1623   (projectile-normalise-patterns (nth 1 (projectile-parse-dirconfig-file))))
1624
1625 (defun projectile-project-ignored ()
1626   "Return list of project ignored files/directories.
1627 Unignored files/directories are not included."
1628   (let ((paths (projectile-paths-to-ignore)))
1629     (projectile-expand-paths paths)))
1630
1631 (defun projectile-unignored-files ()
1632   "Return list of unignored files."
1633   (mapcar
1634    #'projectile-expand-root
1635    (append
1636     projectile-globally-unignored-files
1637     (projectile-project-unignored-files))))
1638
1639 (defun projectile-unignored-directories ()
1640   "Return list of unignored directories."
1641   (mapcar
1642    #'file-name-as-directory
1643    (mapcar
1644     #'projectile-expand-root
1645     (append
1646      projectile-globally-unignored-directories
1647      (projectile-project-unignored-directories)))))
1648
1649 (defun projectile-unignored-directories-rel ()
1650   "Return list of unignored directories, relative to the root."
1651   (projectile-make-relative-to-root (projectile-unignored-directories)))
1652
1653 (defun projectile-unignored-files-rel ()
1654   "Return list of unignored files, relative to the root."
1655   (projectile-make-relative-to-root (projectile-unignored-files)))
1656
1657 (defun projectile-project-unignored-files ()
1658   "Return list of project unignored files."
1659   (cl-remove-if 'file-directory-p (projectile-project-unignored)))
1660
1661 (defun projectile-project-unignored-directories ()
1662   "Return list of project unignored directories."
1663   (cl-remove-if-not 'file-directory-p (projectile-project-unignored)))
1664
1665 (defun projectile-paths-to-ensure ()
1666   "Return a list of unignored project paths."
1667   (projectile-normalise-paths (nth 2 (projectile-parse-dirconfig-file))))
1668
1669 (defun projectile-files-to-ensure ()
1670   (projectile-flatten (mapcar (lambda (pat) (file-expand-wildcards pat t))
1671                               (projectile-patterns-to-ensure))))
1672
1673 (defun projectile-patterns-to-ensure ()
1674   "Return a list of relative file patterns."
1675   (projectile-normalise-patterns (nth 2 (projectile-parse-dirconfig-file))))
1676
1677 (defun projectile-filtering-patterns ()
1678   (cons (projectile-patterns-to-ignore)
1679         (projectile-patterns-to-ensure)))
1680
1681 (defun projectile-project-unignored ()
1682   "Return list of project ignored files/directories."
1683   (delete-dups (append (projectile-expand-paths (projectile-paths-to-ensure))
1684                        (projectile-expand-paths (projectile-files-to-ensure)))))
1685
1686
1687 (defun projectile-dirconfig-file ()
1688   "Return the absolute path to the project's dirconfig file."
1689   (expand-file-name ".projectile" (projectile-project-root)))
1690
1691 (defun projectile-parse-dirconfig-file ()
1692   "Parse project ignore file and return directories to ignore and keep.
1693
1694 The return value will be a list of three elements, the car being
1695 the list of directories to keep, the cadr being the list of files
1696 or directories to ignore, and the caddr being the list of files
1697 or directories to ensure.
1698
1699 Strings starting with + will be added to the list of directories
1700 to keep, and strings starting with - will be added to the list of
1701 directories to ignore.  For backward compatibility, without a
1702 prefix the string will be assumed to be an ignore string."
1703   (let (keep ignore ensure (dirconfig (projectile-dirconfig-file)))
1704     (when (projectile-file-exists-p dirconfig)
1705       (with-temp-buffer
1706         (insert-file-contents dirconfig)
1707         (while (not (eobp))
1708           (pcase (char-after)
1709             (?+ (push (buffer-substring (1+ (point)) (line-end-position)) keep))
1710             (?- (push (buffer-substring (1+ (point)) (line-end-position)) ignore))
1711             (?! (push (buffer-substring (1+ (point)) (line-end-position)) ensure))
1712             (_ (push (buffer-substring (point) (line-end-position)) ignore)))
1713           (forward-line)))
1714       (list (mapcar (lambda (f) (file-name-as-directory (string-trim f)))
1715                     (delete "" (reverse keep)))
1716             (mapcar #'string-trim
1717                     (delete "" (reverse ignore)))
1718             (mapcar #'string-trim
1719                     (delete "" (reverse ensure)))))))
1720
1721 (defun projectile-expand-root (name)
1722   "Expand NAME to project root.
1723
1724 Never use on many files since it's going to recalculate the
1725 project-root for every file."
1726   (expand-file-name name (projectile-project-root)))
1727
1728 (cl-defun projectile-completing-read (prompt choices &key initial-input action)
1729   "Present a project tailored PROMPT with CHOICES."
1730   (let ((prompt (projectile-prepend-project-name prompt))
1731         res)
1732     (setq res
1733           (cond
1734            ((eq projectile-completion-system 'ido)
1735             (ido-completing-read prompt choices nil nil initial-input))
1736            ((eq projectile-completion-system 'default)
1737             (completing-read prompt choices nil nil initial-input))
1738            ((eq projectile-completion-system 'helm)
1739             (if (and (fboundp 'helm)
1740                      (fboundp 'helm-make-source))
1741                 (helm :sources
1742                       (helm-make-source "Projectile" 'helm-source-sync
1743                         :candidates choices
1744                         :action (if action
1745                                     (prog1 action
1746                                       (setq action nil))
1747                                   #'identity))
1748                       :prompt prompt
1749                       :input initial-input
1750                       :buffer "*helm-projectile*")
1751               (user-error "Please install helm from \
1752 https://github.com/emacs-helm/helm")))
1753            ((eq projectile-completion-system 'ivy)
1754             (if (fboundp 'ivy-read)
1755                 (ivy-read prompt choices
1756                           :initial-input initial-input
1757                           :action (prog1 action
1758                                     (setq action nil))
1759                           :caller 'projectile-completing-read)
1760               (user-error "Please install ivy from \
1761 https://github.com/abo-abo/swiper")))
1762            (t (funcall projectile-completion-system prompt choices))))
1763     (if action
1764         (funcall action res)
1765       res)))
1766
1767 (defun projectile-project-files (project-root)
1768   "Return a list of files for the PROJECT-ROOT."
1769   (let (files)
1770     ;; If the cache is too stale, don't use it.
1771     (when projectile-files-cache-expire
1772       (let ((cache-time
1773              (gethash project-root projectile-projects-cache-time)))
1774         (when (or (null cache-time)
1775                   (< (+ cache-time projectile-files-cache-expire)
1776                      (projectile-time-seconds)))
1777           (remhash project-root projectile-projects-cache)
1778           (remhash project-root projectile-projects-cache-time))))
1779
1780     ;; Use the cache, if requested and available.
1781     (when projectile-enable-caching
1782       (setq files (gethash project-root projectile-projects-cache)))
1783
1784     ;; Calculate the list of files.
1785     (when (null files)
1786       (when projectile-enable-caching
1787         (message "Projectile is initializing cache..."))
1788       (setq files
1789             (if (eq projectile-indexing-method 'alien)
1790                 ;; In alien mode we can just skip reading
1791                 ;; .projectile and find all files in the root dir.
1792                 (projectile-dir-files-alien project-root)
1793               ;; If a project is defined as a list of subfolders
1794               ;; then we'll have the files returned for each subfolder,
1795               ;; so they are relative to the project root.
1796               ;;
1797               ;; TODO: That's pretty slow and we need to improve it.
1798               ;; One options would be to pass explicitly the subdirs
1799               ;; to commands like `git ls-files` which would return
1800               ;; files paths relative to the project root.
1801               (cl-mapcan
1802                (lambda (dir)
1803                  (mapcar (lambda (f)
1804                            (file-relative-name (concat dir f)
1805                                                project-root))
1806                          (projectile-dir-files dir)))
1807                (projectile-get-project-directories project-root))))
1808
1809       ;; Save the cached list.
1810       (when projectile-enable-caching
1811         (projectile-cache-project project-root files)))
1812
1813     ;;; Sorting
1814     ;;
1815     ;; Files can't be cached in sorted order as some sorting schemes
1816     ;; require dynamic data.  Sorting is ignored completely when in
1817     ;; alien mode.
1818     (if (eq projectile-indexing-method 'alien)
1819         files
1820       (projectile-sort-files files))))
1821
1822 (defun projectile-current-project-files ()
1823   "Return a list of the files in the current project."
1824   (projectile-project-files (projectile-project-root)))
1825
1826 (defun projectile-process-current-project-files (action)
1827   "Process the current project's files using ACTION."
1828   (let ((project-files (projectile-current-project-files))
1829         (default-directory (projectile-project-root)))
1830     (dolist (filename project-files)
1831       (funcall action filename))))
1832
1833 (defun projectile-project-dirs (project)
1834   "Return a list of dirs for PROJECT."
1835   (delete-dups
1836    (delq nil
1837          (mapcar #'file-name-directory
1838                  (projectile-project-files project)))))
1839
1840 (defun projectile-current-project-dirs ()
1841   "Return a list of dirs for the current project."
1842   (projectile-project-dirs (projectile-ensure-project (projectile-project-root))))
1843
1844 ;;; Interactive commands
1845
1846 (defun projectile--find-other-file (&optional flex-matching ff-variant)
1847   "Switch between files with the same name but different extensions.
1848 With FLEX-MATCHING, match any file that contains the base name of current file.
1849 Other file extensions can be customized with the variable
1850 `projectile-other-file-alist'.  With FF-VARIANT set to a defun, use that
1851 instead of `find-file'.   A typical example of such a defun would be
1852 `find-file-other-window' or `find-file-other-frame'"
1853   (let ((ff (or ff-variant #'find-file))
1854         (other-files (projectile-get-other-files
1855                       (buffer-file-name)
1856                       (projectile-current-project-files)
1857                       flex-matching)))
1858     (if other-files
1859         (let ((file-name (if (= (length other-files) 1)
1860                              (car other-files)
1861                            (projectile-completing-read "Switch to: "
1862                                                        other-files))))
1863           (funcall ff (expand-file-name file-name
1864                                         (projectile-project-root))))
1865       (error "No other file found"))))
1866
1867 ;;;###autoload
1868 (defun projectile-find-other-file (&optional flex-matching)
1869   "Switch between files with the same name but different extensions.
1870 With FLEX-MATCHING, match any file that contains the base name of current file.
1871 Other file extensions can be customized with the variable `projectile-other-file-alist'."
1872   (interactive "P")
1873   (projectile--find-other-file flex-matching))
1874
1875 ;;;###autoload
1876 (defun projectile-find-other-file-other-window (&optional flex-matching)
1877   "Switch between files with the same name but different extensions in other window.
1878 With FLEX-MATCHING, match any file that contains the base name of current file.
1879 Other file extensions can be customized with the variable `projectile-other-file-alist'."
1880   (interactive "P")
1881   (projectile--find-other-file flex-matching
1882                                #'find-file-other-window))
1883
1884 ;;;###autoload
1885 (defun projectile-find-other-file-other-frame (&optional flex-matching)
1886   "Switch between files with the same name but different extensions in other window.
1887 With FLEX-MATCHING, match any file that contains the base name of current file.
1888 Other file extensions can be customized with the variable `projectile-other-file-alist'."
1889   (interactive "P")
1890   (projectile--find-other-file flex-matching
1891                                #'find-file-other-frame))
1892
1893 (defun projectile--file-name-sans-extensions (file-name)
1894   "Return FILE-NAME sans any extensions.
1895 The extensions, in a filename, are what follows the first '.', with the exception of a leading '.'"
1896   (setq file-name (file-name-nondirectory file-name))
1897   (substring file-name 0 (string-match "\\..*" file-name 1)))
1898
1899 (defun projectile--file-name-extensions (file-name)
1900   "Return FILE-NAME's extensions.
1901 The extensions, in a filename, are what follows the first '.', with the exception of a leading '.'"
1902   ;;would it make sense to return nil instead of an empty string if no extensions are found?
1903   (setq file-name (file-name-nondirectory file-name))
1904   (let (extensions-start)
1905     (substring file-name
1906                (if (setq extensions-start (string-match "\\..*" file-name 1))
1907                    (1+ extensions-start)
1908                  (length file-name)))))
1909
1910 (defun projectile-associated-file-name-extensions (file-name)
1911   "Return projectile-other-file-extensions associated to FILE-NAME's extensions.
1912 If no associated other-file-extensions for the complete (nested) extension are found, remove subextensions from FILENAME's extensions until a match is found."
1913   (let ((current-extensions (projectile--file-name-extensions (file-name-nondirectory file-name)))
1914         associated-extensions)
1915     (catch 'break
1916       (while (not (string= "" current-extensions))
1917         (if (setq associated-extensions (cdr (assoc current-extensions projectile-other-file-alist)))
1918             (throw 'break associated-extensions))
1919         (setq current-extensions (projectile--file-name-extensions current-extensions))))))
1920
1921 (defun projectile-get-other-files (current-file project-file-list &optional flex-matching)
1922   "Narrow to files with the same names but different extensions.
1923 Returns a list of possible files for users to choose.
1924
1925 With FLEX-MATCHING, match any file that contains the base name of current file"
1926   (let* ((file-ext-list (projectile-associated-file-name-extensions current-file))
1927          (fulldirname (if (file-name-directory current-file)
1928                           (file-name-directory current-file) "./"))
1929          (dirname (file-name-nondirectory (directory-file-name fulldirname)))
1930          (filename (regexp-quote (projectile--file-name-sans-extensions current-file)))
1931          (file-list (mapcar (lambda (ext)
1932                               (if flex-matching
1933                                   (concat ".*" filename ".*" "\." ext "\\'")
1934                                 (concat "^" filename
1935                                         (unless (equal ext "")
1936                                           (concat "\." ext))
1937                                         "\\'")))
1938                             file-ext-list))
1939          (candidates (cl-remove-if-not
1940                       (lambda (project-file)
1941                         (string-match filename project-file))
1942                       project-file-list))
1943          (candidates
1944           (projectile-flatten (mapcar
1945                                (lambda (file)
1946                                  (cl-remove-if-not
1947                                   (lambda (project-file)
1948                                     (string-match file
1949                                                   (concat (file-name-base project-file)
1950                                                           (unless (equal (file-name-extension project-file) nil)
1951                                                             (concat "\." (file-name-extension project-file))))))
1952                                   candidates))
1953                                file-list)))
1954          (candidates
1955           (cl-remove-if-not (lambda (file) (not (backup-file-name-p file))) candidates))
1956          (candidates
1957           (cl-sort (copy-sequence candidates)
1958                    (lambda (file _)
1959                      (let ((candidate-dirname (file-name-nondirectory (directory-file-name (file-name-directory file)))))
1960                        (unless (equal fulldirname (file-name-directory file))
1961                          (equal dirname candidate-dirname)))))))
1962     candidates))
1963
1964 (defun projectile-select-files (project-files &optional invalidate-cache)
1965   "Select a list of files based on filename at point.
1966
1967 With a prefix arg INVALIDATE-CACHE invalidates the cache first."
1968   (projectile-maybe-invalidate-cache invalidate-cache)
1969   (let* ((file (if (region-active-p)
1970                    (buffer-substring (region-beginning) (region-end))
1971                  (or (thing-at-point 'filename) "")))
1972          (file (if (string-match "\\.?\\./" file)
1973                    (file-relative-name (file-truename file) (projectile-project-root))
1974                  file))
1975          (files (if file
1976                     (cl-remove-if-not
1977                      (lambda (project-file)
1978                        (string-match file project-file))
1979                      project-files)
1980                   nil)))
1981     files))
1982
1983 (defun projectile--find-file-dwim (invalidate-cache &optional ff-variant)
1984   "Jump to a project's files using completion based on context.
1985
1986 With a INVALIDATE-CACHE invalidates the cache first.
1987
1988 With FF-VARIANT set to a defun, use that instead of `find-file'.
1989 A typical example of such a defun would be `find-file-other-window' or
1990 `find-file-other-frame'
1991
1992 Subroutine for `projectile-find-file-dwim' and
1993 `projectile-find-file-dwim-other-window'"
1994   (let* ((project-root (projectile-project-root))
1995          (project-files (projectile-project-files project-root))
1996          (files (projectile-select-files project-files invalidate-cache))
1997          (file (cond ((= (length files) 1)
1998                       (car files))
1999                      ((> (length files) 1)
2000                       (projectile-completing-read "Switch to: " files))
2001                      (t
2002                       (projectile-completing-read "Switch to: " project-files))))
2003          (ff (or ff-variant #'find-file)))
2004     (funcall ff (expand-file-name file project-root))
2005     (run-hooks 'projectile-find-file-hook)))
2006
2007 ;;;###autoload
2008 (defun projectile-find-file-dwim (&optional invalidate-cache)
2009   "Jump to a project's files using completion based on context.
2010
2011 With a prefix arg INVALIDATE-CACHE invalidates the cache first.
2012
2013 If point is on a filename, Projectile first tries to search for that
2014 file in project:
2015
2016 - If it finds just a file, it switches to that file instantly.  This works even
2017 if the filename is incomplete, but there's only a single file in the current project
2018 that matches the filename at point.  For example, if there's only a single file named
2019 \"projectile/projectile.el\" but the current filename is \"projectile/proj\" (incomplete),
2020 `projectile-find-file-dwim' still switches to \"projectile/projectile.el\" immediately
2021  because this is the only filename that matches.
2022
2023 - If it finds a list of files, the list is displayed for selecting.  A list of
2024 files is displayed when a filename appears more than one in the project or the
2025 filename at point is a prefix of more than two files in a project.  For example,
2026 if `projectile-find-file-dwim' is executed on a filepath like \"projectile/\", it lists
2027 the content of that directory.  If it is executed on a partial filename like
2028  \"projectile/a\", a list of files with character 'a' in that directory is presented.
2029
2030 - If it finds nothing, display a list of all files in project for selecting."
2031   (interactive "P")
2032   (projectile--find-file-dwim invalidate-cache))
2033
2034 ;;;###autoload
2035 (defun projectile-find-file-dwim-other-window (&optional invalidate-cache)
2036   "Jump to a project's files using completion based on context in other window.
2037
2038 With a prefix arg INVALIDATE-CACHE invalidates the cache first.
2039
2040 If point is on a filename, Projectile first tries to search for that
2041 file in project:
2042
2043 - If it finds just a file, it switches to that file instantly.  This works even
2044 if the filename is incomplete, but there's only a single file in the current project
2045 that matches the filename at point.  For example, if there's only a single file named
2046 \"projectile/projectile.el\" but the current filename is \"projectile/proj\" (incomplete),
2047 `projectile-find-file-dwim-other-window' still switches to \"projectile/projectile.el\"
2048 immediately because this is the only filename that matches.
2049
2050 - If it finds a list of files, the list is displayed for selecting.  A list of
2051 files is displayed when a filename appears more than one in the project or the
2052 filename at point is a prefix of more than two files in a project.  For example,
2053 if `projectile-find-file-dwim-other-window' is executed on a filepath like \"projectile/\", it lists
2054 the content of that directory.  If it is executed on a partial filename
2055 like \"projectile/a\", a list of files with character 'a' in that directory
2056 is presented.
2057
2058 - If it finds nothing, display a list of all files in project for selecting."
2059   (interactive "P")
2060   (projectile--find-file-dwim invalidate-cache #'find-file-other-window))
2061
2062 ;;;###autoload
2063 (defun projectile-find-file-dwim-other-frame (&optional invalidate-cache)
2064   "Jump to a project's files using completion based on context in other frame.
2065
2066 With a prefix arg INVALIDATE-CACHE invalidates the cache first.
2067
2068 If point is on a filename, Projectile first tries to search for that
2069 file in project:
2070
2071 - If it finds just a file, it switches to that file instantly.  This works even
2072 if the filename is incomplete, but there's only a single file in the current project
2073 that matches the filename at point.  For example, if there's only a single file named
2074 \"projectile/projectile.el\" but the current filename is \"projectile/proj\" (incomplete),
2075 `projectile-find-file-dwim-other-frame' still switches to \"projectile/projectile.el\"
2076 immediately because this is the only filename that matches.
2077
2078 - If it finds a list of files, the list is displayed for selecting.  A list of
2079 files is displayed when a filename appears more than one in the project or the
2080 filename at point is a prefix of more than two files in a project.  For example,
2081 if `projectile-find-file-dwim-other-frame' is executed on a filepath like \"projectile/\", it lists
2082 the content of that directory.  If it is executed on a partial filename
2083 like \"projectile/a\", a list of files with character 'a' in that directory
2084 is presented.
2085
2086 - If it finds nothing, display a list of all files in project for selecting."
2087   (interactive "P")
2088   (projectile--find-file-dwim invalidate-cache #'find-file-other-frame))
2089
2090 (defun projectile--find-file (invalidate-cache &optional ff-variant)
2091   "Jump to a project's file using completion.
2092 With INVALIDATE-CACHE invalidates the cache first.  With FF-VARIANT set to a
2093 defun, use that instead of `find-file'.   A typical example of such a defun
2094 would be `find-file-other-window' or `find-file-other-frame'"
2095   (interactive "P")
2096   (projectile-maybe-invalidate-cache invalidate-cache)
2097   (let* ((project-root (projectile-ensure-project (projectile-project-root)))
2098          (file (projectile-completing-read "Find file: "
2099                                           (projectile-project-files project-root)))
2100          (ff (or ff-variant #'find-file)))
2101     (when file
2102       (funcall ff (expand-file-name file project-root))
2103       (run-hooks 'projectile-find-file-hook))))
2104
2105 ;;;###autoload
2106 (defun projectile-find-file (&optional invalidate-cache)
2107   "Jump to a project's file using completion.
2108 With a prefix arg INVALIDATE-CACHE invalidates the cache first."
2109   (interactive "P")
2110   (projectile--find-file invalidate-cache))
2111
2112 ;;;###autoload
2113 (defun projectile-find-file-other-window (&optional invalidate-cache)
2114   "Jump to a project's file using completion and show it in another window.
2115
2116 With a prefix arg INVALIDATE-CACHE invalidates the cache first."
2117   (interactive "P")
2118   (projectile--find-file invalidate-cache #'find-file-other-window))
2119
2120 ;;;###autoload
2121 (defun projectile-find-file-other-frame (&optional invalidate-cache)
2122   "Jump to a project's file using completion and show it in another frame.
2123
2124 With a prefix arg INVALIDATE-CACHE invalidates the cache first."
2125   (interactive "P")
2126   (projectile--find-file invalidate-cache #'find-file-other-frame))
2127
2128 ;;;###autoload
2129 (defun projectile-toggle-project-read-only ()
2130   "Toggle project read only."
2131   (interactive)
2132   (let ((inhibit-read-only t)
2133         (val (not buffer-read-only))
2134         (default-directory (projectile-ensure-project (projectile-project-root))))
2135     (add-dir-local-variable nil 'buffer-read-only val)
2136     (save-buffer)
2137     (kill-buffer)
2138     (when buffer-file-name
2139       (read-only-mode (if val +1 -1))
2140       (message "[%s] read-only-mode is %s" (projectile-project-name) (if val "on" "off")))))
2141
2142
2143 ;;;; Sorting project files
2144 (defun projectile-sort-files (files)
2145   "Sort FILES according to `projectile-sort-order'."
2146   (cl-case projectile-sort-order
2147     (default files)
2148     (recentf (projectile-sort-by-recentf-first files))
2149     (recently-active (projectile-sort-by-recently-active-first files))
2150     (modification-time (projectile-sort-by-modification-time files))
2151     (access-time (projectile-sort-by-access-time files))))
2152
2153 (defun projectile-sort-by-recentf-first (files)
2154   "Sort FILES by a recent first scheme."
2155   (let ((project-recentf-files (projectile-recentf-files)))
2156     (append project-recentf-files
2157             (projectile-difference files project-recentf-files))))
2158
2159 (defun projectile-sort-by-recently-active-first (files)
2160   "Sort FILES by most recently active buffers or opened files."
2161   (let ((project-recently-active-files (projectile-recently-active-files)))
2162     (append project-recently-active-files
2163             (projectile-difference files project-recently-active-files))))
2164
2165 (defun projectile-sort-by-modification-time (files)
2166   "Sort FILES by modification time."
2167   (let ((default-directory (projectile-project-root)))
2168     (cl-sort
2169      (copy-sequence files)
2170      (lambda (file1 file2)
2171        (let ((file1-mtime (nth 5 (file-attributes file1)))
2172              (file2-mtime (nth 5 (file-attributes file2))))
2173          (not (time-less-p file1-mtime file2-mtime)))))))
2174
2175 (defun projectile-sort-by-access-time (files)
2176   "Sort FILES by access time."
2177   (let ((default-directory (projectile-project-root)))
2178     (cl-sort
2179      (copy-sequence files)
2180      (lambda (file1 file2)
2181        (let ((file1-atime (nth 4 (file-attributes file1)))
2182              (file2-atime (nth 4 (file-attributes file2))))
2183          (not (time-less-p file1-atime file2-atime)))))))
2184
2185
2186 ;;;; Find directory in project functionality
2187 (defun projectile--find-dir (invalidate-cache &optional dired-variant)
2188   "Jump to a project's directory using completion.
2189
2190 With INVALIDATE-CACHE invalidates the cache first.  With DIRED-VARIANT set to a
2191 defun, use that instead of `dired'.  A typical example of such a defun would be
2192 `dired-other-window' or `dired-other-frame'"
2193   (projectile-maybe-invalidate-cache invalidate-cache)
2194   (let* ((project (projectile-ensure-project (projectile-project-root)))
2195          (dir (projectile-complete-dir project))
2196          (dired-v (or dired-variant #'dired)))
2197     (funcall dired-v (expand-file-name dir project))
2198     (run-hooks 'projectile-find-dir-hook)))
2199
2200 ;;;###autoload
2201 (defun projectile-find-dir (&optional invalidate-cache)
2202   "Jump to a project's directory using completion.
2203
2204 With a prefix arg INVALIDATE-CACHE invalidates the cache first."
2205   (interactive "P")
2206   (projectile--find-dir invalidate-cache))
2207
2208 ;;;###autoload
2209 (defun projectile-find-dir-other-window (&optional invalidate-cache)
2210   "Jump to a project's directory in other window using completion.
2211
2212 With a prefix arg INVALIDATE-CACHE invalidates the cache first."
2213   (interactive "P")
2214   (projectile--find-dir invalidate-cache #'dired-other-window))
2215
2216 ;;;###autoload
2217 (defun projectile-find-dir-other-frame (&optional invalidate-cache)
2218   "Jump to a project's directory in other window using completion.
2219
2220 With a prefix arg INVALIDATE-CACHE invalidates the cache first."
2221   (interactive "P")
2222   (projectile--find-dir invalidate-cache #'dired-other-frame))
2223
2224 (defun projectile-complete-dir (project)
2225   (let ((project-dirs (projectile-project-dirs project)))
2226     (projectile-completing-read
2227     "Find dir: "
2228     (if projectile-find-dir-includes-top-level
2229         (append '("./") project-dirs)
2230       project-dirs))))
2231
2232 ;;;###autoload
2233 (defun projectile-find-test-file (&optional invalidate-cache)
2234   "Jump to a project's test file using completion.
2235
2236 With a prefix arg INVALIDATE-CACHE invalidates the cache first."
2237   (interactive "P")
2238   (projectile-maybe-invalidate-cache invalidate-cache)
2239   (let ((file (projectile-completing-read "Find test file: "
2240                                           (projectile-current-project-test-files))))
2241     (find-file (expand-file-name file (projectile-project-root)))))
2242
2243 (defun projectile-test-files (files)
2244   "Return only the test FILES."
2245   (cl-remove-if-not 'projectile-test-file-p files))
2246
2247 (defun projectile-test-file-p (file)
2248   "Check if FILE is a test file."
2249   (or (cl-some (lambda (pat) (string-prefix-p pat (file-name-nondirectory file)))
2250                (delq nil (list (funcall projectile-test-prefix-function (projectile-project-type)))))
2251       (cl-some (lambda (pat) (string-suffix-p pat (file-name-sans-extension (file-name-nondirectory file))))
2252                (delq nil (list (funcall projectile-test-suffix-function (projectile-project-type)))))))
2253
2254 (defun projectile-current-project-test-files ()
2255   "Return a list of test files for the current project."
2256   (projectile-test-files (projectile-current-project-files)))
2257
2258 (defvar projectile-project-types nil
2259   "An alist holding all project types that are known to Projectile.
2260 The project types are symbols and they are linked to plists holding
2261 the properties of the various project types.")
2262
2263 (cl-defun projectile-register-project-type
2264     (project-type marker-files &key compilation-dir configure compile test run test-suffix test-prefix src-dir test-dir)
2265   "Register a project type with projectile.
2266
2267 A project type is defined by PROJECT-TYPE, a set of MARKER-FILES,
2268 and optional keyword arguments:
2269 COMPILATION-DIR the directory to run the tests- and compilations in,
2270 CONFIGURE which specifies a command that configures the project
2271           `%s' in the command will be substituted with (projectile-project-root)
2272           before the command is run,
2273 COMPILE which specifies a command that builds the project,
2274 TEST which specified a command that tests the project,
2275 RUN which specifies a command that runs the project,
2276 TEST-SUFFIX which specifies test file suffix, and
2277 TEST-PREFIX which specifies test file prefix.
2278 SRC-DIR which specifies the path to the source relative to the project root.
2279 TEST-DIR which specifies the path to the tests relative to the project root."
2280   (let ((project-plist (list 'marker-files marker-files
2281                              'compilation-dir compilation-dir
2282                              'configure-command configure
2283                              'compile-command compile
2284                              'test-command test
2285                              'run-command run)))
2286     ;; There is no way for the function to distinguish between an
2287     ;; explicit argument of nil and an omitted argument. However, the
2288     ;; body of the function is free to consider nil an abbreviation
2289     ;; for some other meaningful value
2290     (when test-suffix
2291       (plist-put project-plist 'test-suffix test-suffix))
2292     (when test-prefix
2293       (plist-put project-plist 'test-prefix test-prefix))
2294     (when src-dir
2295       (plist-put project-plist 'src-dir src-dir))
2296     (when test-dir
2297       (plist-put project-plist 'test-dir test-dir))
2298     (setq projectile-project-types
2299           (cons `(,project-type . ,project-plist)
2300                 projectile-project-types))))
2301
2302 (defun projectile-cabal-project-p ()
2303   "Check if a project contains *.cabal files but no stack.yaml file."
2304   (and (projectile-verify-file-wildcard "*.cabal")
2305        (not (projectile-verify-file "stack.yaml"))))
2306
2307 (defun projectile-go-project-p ()
2308   "Check if a project contains Go source files."
2309   (projectile-verify-file-wildcard "*.go"))
2310
2311 (define-obsolete-variable-alias 'projectile-go-function 'projectile-go-project-test-function "1.0.0")
2312 (defcustom projectile-go-project-test-function #'projectile-go-project-p
2313   "Function to determine if project's type is go."
2314   :group 'projectile
2315   :type 'function)
2316
2317 ;;; Project type registration
2318 ;;
2319 ;; Project type detection happens in a reverse order with respect to
2320 ;; project type registration (invocations of `projectile-register-project-type').
2321 ;;
2322 ;; As function-based project type detection is pretty slow, so it
2323 ;; should be tried at the end if everything else failed (meaning here
2324 ;; it should be listed first).
2325 ;;
2326 ;; Ideally common project types should be checked earlier than exotic ones.
2327
2328 ;; Function-based detection project type
2329 (projectile-register-project-type 'haskell-cabal #'projectile-cabal-project-p
2330                                   :compile "cabal build"
2331                                   :test "cabal test"
2332                                   :test-suffix "Spec")
2333 (projectile-register-project-type 'go projectile-go-project-test-function
2334                                   :compile "go build ./..."
2335                                   :test "go test ./..."
2336                                   :test-suffix "_test")
2337 ;; File-based detection project types
2338
2339 ;; Universal
2340 (projectile-register-project-type 'scons '("SConstruct")
2341                                   :compile "scons"
2342                                   :test "scons test"
2343                                   :test-suffix "test")
2344 (projectile-register-project-type 'meson '("meson.build")
2345                                   :compilation-dir "build"
2346                                   :configure "meson %s"
2347                                   :compile "ninja"
2348                                   :test "ninja test")
2349 (projectile-register-project-type 'nix '("default.nix")
2350                                   :compile "nix-build"
2351                                   :test "nix-build")
2352 ;; Make & CMake
2353 (projectile-register-project-type 'make '("Makefile")
2354                                   :compile "make"
2355                                   :test "make test")
2356 (projectile-register-project-type 'cmake '("CMakeLists.txt")
2357                                   :configure "cmake %s"
2358                                   :compile "cmake --build ."
2359                                   :test "ctest")
2360 ;; PHP
2361 (projectile-register-project-type 'php-symfony '("composer.json" "app" "src" "vendor")
2362                                   :compile "app/console server:run"
2363                                   :test "phpunit -c app "
2364                                   :test-suffix "Test")
2365 ;; Erlang & Elixir
2366 (projectile-register-project-type 'rebar '("rebar.config")
2367                                   :compile "rebar"
2368                                   :test "rebar eunit"
2369                                   :test-suffix "_SUITE")
2370 (projectile-register-project-type 'elixir '("mix.exs")
2371                                   :compile "mix compile"
2372                                   :src-dir "lib/"
2373                                   :test "mix test"
2374                                   :test-suffix "_test")
2375 ;; JavaScript
2376 (projectile-register-project-type 'grunt '("Gruntfile.js")
2377                                   :compile "grunt"
2378                                   :test "grunt test")
2379 (projectile-register-project-type 'gulp '("gulpfile.js")
2380                                   :compile "gulp"
2381                                   :test "gulp test")
2382 (projectile-register-project-type 'npm '("package.json")
2383                                   :compile "npm install"
2384                                   :test "npm test"
2385                                   :test-suffix ".test")
2386 ;; Angular
2387 (projectile-register-project-type 'angular '("angular.json" ".angular-cli.json")
2388                                   :compile "ng build"
2389                                   :run "ng serve"
2390                                   :test "ng test")
2391 ;; Python
2392 (projectile-register-project-type 'django '("manage.py")
2393                                   :compile "python manage.py runserver"
2394                                   :test "python manage.py test"
2395                                   :test-prefix "test_"
2396                                   :test-suffix"_test")
2397 (projectile-register-project-type 'python-pip '("requirements.txt")
2398                                   :compile "python setup.by build"
2399                                   :test "python -m unittest discover"
2400                                   :test-prefix "test_"
2401                                   :test-suffix"_test")
2402 (projectile-register-project-type 'python-pkg '("setup.py")
2403                                   :compile "python setup.py build"
2404                                   :test "python -m unittest discover"
2405                                   :test-prefix "test_"
2406                                   :test-suffix"_test")
2407 (projectile-register-project-type 'python-tox '("tox.ini")
2408                                   :compile "tox -r --notest"
2409                                   :test "tox"
2410                                   :test-prefix "test_"
2411                                   :test-suffix"_test")
2412 (projectile-register-project-type 'python-pipenv '("Pipfile")
2413                                   :compile "pipenv run build"
2414                                   :test "pipenv run test"
2415                                   :test-prefix "test_"
2416                                   :test-suffix "_test")
2417 ;; Java & friends
2418 (projectile-register-project-type 'maven '("pom.xml")
2419                                   :compile "mvn clean install"
2420                                   :test "mvn test"
2421                                   :test-suffix "Test"
2422                                   :src-dir "main/src/"
2423                                   :test-dir "main/test/")
2424 (projectile-register-project-type 'gradle '("build.gradle")
2425                                   :compile "gradle build"
2426                                   :test "gradle test"
2427                                   :test-suffix "Spec")
2428 (projectile-register-project-type 'gradlew '("gradlew")
2429                                   :compile "./gradlew build"
2430                                   :test "./gradlew test"
2431                                   :test-suffix "Spec")
2432 (projectile-register-project-type 'grails '("application.properties" "grails-app")
2433                                   :compile "grails package"
2434                                   :test "grails test-app"
2435                                   :test-suffix "Spec")
2436 (projectile-register-project-type 'sbt '("build.sbt")
2437                                   :compile "sbt compile"
2438                                   :test "sbt test"
2439                                   :test-suffix "Spec")
2440 (projectile-register-project-type 'lein-test '("project.clj")
2441                                   :compile "lein compile"
2442                                   :test "lein test"
2443                                   :test-suffix "_test")
2444 (projectile-register-project-type 'lein-midje '("project.clj" ".midje.clj")
2445                                   :compile "lein compile"
2446                                   :test "lein midje"
2447                                   :test-prefix "t_")
2448 (projectile-register-project-type 'boot-clj '("build.boot")
2449                                   :compile "boot aot"
2450                                   :test "boot test"
2451                                   :test-suffix "_test")
2452 (projectile-register-project-type 'clojure-cli '("deps.edn")
2453                                   :test-suffix "_test")
2454 ;; Ruby
2455 (projectile-register-project-type 'ruby-rspec '("Gemfile" "lib" "spec")
2456                                   :compile "bundle exec rake"
2457                                   :src-dir "lib/"
2458                                   :test "bundle exec rspec"
2459                                   :test-dir "spec/"
2460                                   :test-suffix "_spec")
2461 (projectile-register-project-type 'ruby-test '("Gemfile" "lib" "test")
2462                                   :compile"bundle exec rake"
2463                                   :src-dir "lib/"
2464                                   :test "bundle exec rake test"
2465                                   :test-suffix "_test")
2466 ;; Rails needs to be registered after npm, otherwise `package.json` makes it `npm`.
2467 ;; https://github.com/bbatsov/projectile/pull/1191
2468 (projectile-register-project-type 'rails-test '("Gemfile" "app" "lib" "db" "config" "test")
2469                                   :compile "bundle exec rails server"
2470                                   :src-dir "lib/"
2471                                   :test "bundle exec rake test"
2472                                   :test-suffix "_test")
2473 (projectile-register-project-type 'rails-rspec '("Gemfile" "app" "lib" "db" "config" "spec")
2474                                   :compile "bundle exec rails server"
2475                                   :src-dir "lib/"
2476                                   :test "bundle exec rspec"
2477                                   :test-dir "spec/"
2478                                   :test-suffix "_spec")
2479 ;; Crystal
2480 (projectile-register-project-type 'crystal-spec '("shard.yml")
2481                                   :src-dir "src/"
2482                                   :test "crystal spec"
2483                                   :test-dir "spec/"
2484                                   :test-suffix "_spec")
2485
2486 ;; Emacs
2487 (projectile-register-project-type 'emacs-cask '("Cask")
2488                                   :compile "cask install"
2489                                   :test-prefix "test-"
2490                                   :test-suffix "-test")
2491
2492 ;; R
2493 (projectile-register-project-type 'r '("DESCRIPTION")
2494                                   :compile "R CMD INSTALL --with-keep.source ."
2495                                   :test (concat "R CMD check -o " temporary-file-directory " ."))
2496
2497 ;; Haskell
2498 (projectile-register-project-type 'haskell-stack '("stack.yaml")
2499                                   :compile "stack build"
2500                                   :test "stack build --test"
2501                                   :test-suffix "Spec")
2502
2503 ;; Rust
2504 (projectile-register-project-type 'rust-cargo '("Cargo.toml")
2505                                   :compile "cargo build"
2506                                   :test "cargo test")
2507
2508 ;; Racket
2509 (projectile-register-project-type 'racket '("info.rkt")
2510                                   :test "raco test .")
2511
2512
2513 (defvar-local projectile-project-type nil
2514   "Buffer local var for overriding the auto-detected project type.
2515 Normally you'd set this from .dir-locals.el.")
2516 (put 'projectile-project-type 'safe-local-variable #'symbolp)
2517
2518 (defun projectile-detect-project-type ()
2519   "Detect the type of the current project.
2520 Fallsback to a generic project type when the type can't be determined."
2521   (let ((project-type
2522          (or (car (cl-find-if
2523                    (lambda (project-type-record)
2524                      (let ((project-type (car project-type-record))
2525                            (marker (plist-get (cdr project-type-record) 'marker-files)))
2526                        (if (listp marker)
2527                            (and (projectile-verify-files marker) project-type)
2528                          (and (funcall marker) project-type))))
2529                    projectile-project-types))
2530              'generic)))
2531     (puthash (projectile-project-root) project-type projectile-project-type-cache)
2532     project-type))
2533
2534 (defun projectile-project-type (&optional dir)
2535   "Determine a project's type based on its structure.
2536 When DIR is specified it checks it, otherwise it acts
2537 on the current project.
2538
2539 The project type is cached for improved performance."
2540   (if projectile-project-type
2541       projectile-project-type
2542     (let* ((dir (or dir default-directory))
2543            (project-root (projectile-project-root dir)))
2544       (if project-root
2545           (or (gethash project-root projectile-project-type-cache)
2546               (projectile-detect-project-type))
2547         ;; if we're not in a project we just return nil
2548         nil))))
2549
2550 ;;;###autoload
2551 (defun projectile-project-info ()
2552   "Display info for current project."
2553   (interactive)
2554   (message "Project dir: %s ## Project VCS: %s ## Project type: %s"
2555            (projectile-project-root)
2556            (projectile-project-vcs)
2557            (projectile-project-type)))
2558
2559 (defun projectile-verify-files (files)
2560   "Check whether all FILES exist in the current project."
2561   (cl-every #'projectile-verify-file files))
2562
2563 (defun projectile-verify-file (file)
2564   "Check whether FILE exists in the current project."
2565   (file-exists-p (projectile-expand-root file)))
2566
2567 (defun projectile-verify-file-wildcard (file)
2568   "Check whether FILE exists in the current project.
2569 Expands wildcards using `file-expand-wildcards' before checking."
2570   (file-expand-wildcards (projectile-expand-root file)))
2571
2572 (defun projectile-project-vcs (&optional project-root)
2573   "Determine the VCS used by the project if any.
2574 PROJECT-ROOT is the targeted directory.  If nil, use
2575 `projectile-project-root'."
2576   (or project-root (setq project-root (projectile-project-root)))
2577   (cond
2578    ((projectile-file-exists-p (expand-file-name ".git" project-root)) 'git)
2579    ((projectile-file-exists-p (expand-file-name ".hg" project-root)) 'hg)
2580    ((projectile-file-exists-p (expand-file-name ".fslckout" project-root)) 'fossil)
2581    ((projectile-file-exists-p (expand-file-name "_FOSSIL_" project-root)) 'fossil)
2582    ((projectile-file-exists-p (expand-file-name ".bzr" project-root)) 'bzr)
2583    ((projectile-file-exists-p (expand-file-name "_darcs" project-root)) 'darcs)
2584    ((projectile-file-exists-p (expand-file-name ".svn" project-root)) 'svn)
2585    ((projectile-locate-dominating-file project-root ".git") 'git)
2586    ((projectile-locate-dominating-file project-root ".hg") 'hg)
2587    ((projectile-locate-dominating-file project-root ".fslckout") 'fossil)
2588    ((projectile-locate-dominating-file project-root "_FOSSIL_") 'fossil)
2589    ((projectile-locate-dominating-file project-root ".bzr") 'bzr)
2590    ((projectile-locate-dominating-file project-root "_darcs") 'darcs)
2591    ((projectile-locate-dominating-file project-root ".svn") 'svn)
2592    (t 'none)))
2593
2594 (defun projectile--test-name-for-impl-name (impl-file-path)
2595   "Determine the name of the test file for IMPL-FILE-PATH."
2596   (let* ((project-type (projectile-project-type))
2597          (impl-file-name (file-name-sans-extension (file-name-nondirectory impl-file-path)))
2598          (impl-file-ext (file-name-extension impl-file-path))
2599          (test-prefix (funcall projectile-test-prefix-function project-type))
2600          (test-suffix (funcall projectile-test-suffix-function project-type)))
2601     (cond
2602      (test-prefix (concat test-prefix impl-file-name "." impl-file-ext))
2603      (test-suffix (concat impl-file-name test-suffix "." impl-file-ext))
2604      (t (error "Project type `%s' not supported!" project-type)))))
2605
2606 (defun projectile-create-test-file-for (impl-file-path)
2607   "Create a test file for IMPL-FILE-PATH."
2608   (let* ((test-file (projectile--test-name-for-impl-name impl-file-path))
2609          (project-root (projectile-project-root))
2610          (relative-dir (file-name-directory (file-relative-name impl-file-path project-root)))
2611          (src-dir-name (projectile-src-directory (projectile-project-type)))
2612          (test-dir-name (projectile-test-directory (projectile-project-type)))
2613          (test-dir (expand-file-name (replace-regexp-in-string src-dir-name test-dir-name relative-dir) project-root))
2614          (test-path (expand-file-name test-file test-dir)))
2615     (unless (file-exists-p test-path)
2616       (progn (unless (file-exists-p test-dir)
2617                (make-directory test-dir :create-parents))
2618              test-path))))
2619
2620 (defun projectile-find-implementation-or-test (file-name)
2621   "Given a FILE-NAME return the matching implementation or test filename.
2622
2623 If `projectile-create-missing-test-files' is non-nil, create the missing
2624 test file."
2625   (unless file-name (error "The current buffer is not visiting a file"))
2626   (if (projectile-test-file-p file-name)
2627       ;; find the matching impl file
2628       (let ((impl-file (projectile-find-matching-file file-name)))
2629         (if impl-file
2630             (projectile-expand-root impl-file)
2631           (error
2632            "No matching source file found for project type `%s'"
2633            (projectile-project-type))))
2634     ;; find the matching test file
2635     (let ((test-file (projectile-find-matching-test file-name)))
2636       (if test-file
2637           (projectile-expand-root test-file)
2638         (if projectile-create-missing-test-files
2639             (projectile-create-test-file-for file-name)
2640           (error "No matching test file found for project type `%s'"
2641                  (projectile-project-type)))))))
2642
2643 ;;;###autoload
2644 (defun projectile-find-implementation-or-test-other-window ()
2645   "Open matching implementation or test file in other window."
2646   (interactive)
2647   (find-file-other-window
2648    (projectile-find-implementation-or-test (buffer-file-name))))
2649
2650 ;;;###autoload
2651 (defun projectile-find-implementation-or-test-other-frame ()
2652   "Open matching implementation or test file in other frame."
2653   (interactive)
2654   (find-file-other-frame
2655    (projectile-find-implementation-or-test (buffer-file-name))))
2656
2657 ;;;###autoload
2658 (defun projectile-toggle-between-implementation-and-test ()
2659   "Toggle between an implementation file and its test file."
2660   (interactive)
2661   (find-file
2662    (projectile-find-implementation-or-test (buffer-file-name))))
2663
2664
2665 (defun projectile-project-type-attribute (project-type key &optional default-value)
2666   "Return the value of some PROJECT-TYPE attribute identified by KEY.
2667 Fallback to DEFAULT-VALUE for missing attributes."
2668   (let ((project (alist-get project-type projectile-project-types)))
2669     (if (and project (plist-member project key))
2670         (plist-get project key)
2671       default-value)))
2672
2673 (defun projectile-test-prefix (project-type)
2674   "Find default test files prefix based on PROJECT-TYPE."
2675   (projectile-project-type-attribute project-type 'test-prefix))
2676
2677 (defun projectile-test-suffix (project-type)
2678   "Find default test files suffix based on PROJECT-TYPE."
2679   (projectile-project-type-attribute project-type 'test-suffix))
2680
2681 (defun projectile-src-directory (project-type)
2682   "Find default src directory based on PROJECT-TYPE."
2683   (projectile-project-type-attribute project-type 'src-dir "src/"))
2684
2685 (defun projectile-test-directory (project-type)
2686   "Find default test directory based on PROJECT-TYPE."
2687   (projectile-project-type-attribute project-type 'test-dir "test/"))
2688
2689 (defun projectile-dirname-matching-count (a b)
2690   "Count matching dirnames ascending file paths."
2691   (setq a (reverse (split-string (or (file-name-directory a) "") "/" t))
2692         b (reverse (split-string (or (file-name-directory b) "") "/" t)))
2693   (let ((common 0))
2694     (while (and a b (string-equal (pop a) (pop b)))
2695       (setq common (1+ common)))
2696     common))
2697
2698 (defun projectile-group-file-candidates (file candidates)
2699   "Group file candidates by dirname matching count."
2700   (cl-sort (copy-sequence
2701             (let (value result)
2702               (while (setq value (pop candidates))
2703                 (let* ((key (projectile-dirname-matching-count file value))
2704                        (kv (assoc key result)))
2705                   (if kv
2706                       (setcdr kv (cons value (cdr kv)))
2707                     (push (list key value) result))))
2708               (mapcar (lambda (x)
2709                         (cons (car x) (nreverse (cdr x))))
2710                       (nreverse result))))
2711            (lambda (a b) (> (car a) (car b)))))
2712
2713 (defun projectile-find-matching-test (file)
2714   "Compute the name of the test matching FILE."
2715   (let* ((basename (file-name-nondirectory (file-name-sans-extension file)))
2716          (test-prefix (funcall projectile-test-prefix-function (projectile-project-type)))
2717          (test-suffix (funcall projectile-test-suffix-function (projectile-project-type)))
2718          (candidates
2719           (cl-remove-if-not
2720            (lambda (current-file)
2721              (let ((name (file-name-nondirectory
2722                           (file-name-sans-extension current-file))))
2723                (or (when test-prefix
2724                      (string-equal name (concat test-prefix basename)))
2725                    (when test-suffix
2726                      (string-equal name (concat basename test-suffix))))))
2727            (projectile-current-project-files))))
2728     (cond
2729      ((null candidates) nil)
2730      ((= (length candidates) 1) (car candidates))
2731      (t (let ((grouped-candidates (projectile-group-file-candidates file candidates)))
2732           (if (= (length (car grouped-candidates)) 2)
2733               (car (last (car grouped-candidates)))
2734             (projectile-completing-read
2735              "Switch to: "
2736              (apply 'append (mapcar 'cdr grouped-candidates)))))))))
2737
2738 (defun projectile-find-matching-file (test-file)
2739   "Compute the name of a file matching TEST-FILE."
2740   (let* ((basename (file-name-nondirectory (file-name-sans-extension test-file)))
2741          (test-prefix (funcall projectile-test-prefix-function (projectile-project-type)))
2742          (test-suffix (funcall projectile-test-suffix-function (projectile-project-type)))
2743          (candidates
2744           (cl-remove-if-not
2745            (lambda (current-file)
2746              (let ((name (file-name-nondirectory
2747                           (file-name-sans-extension current-file))))
2748                (or (when test-prefix
2749                      (string-equal (concat test-prefix name) basename))
2750                    (when test-suffix
2751                      (string-equal (concat name test-suffix) basename)))))
2752            (projectile-current-project-files))))
2753     (cond
2754      ((null candidates) nil)
2755      ((= (length candidates) 1) (car candidates))
2756      (t (let ((grouped-candidates (projectile-group-file-candidates test-file candidates)))
2757           (if (= (length (car grouped-candidates)) 2)
2758               (car (last (car grouped-candidates)))
2759             (projectile-completing-read
2760              "Switch to: "
2761              (apply 'append (mapcar 'cdr grouped-candidates)))))))))
2762
2763 (defun projectile-grep-default-files ()
2764   "Try to find a default pattern for `projectile-grep'.
2765 This is a subset of `grep-read-files', where either a matching entry from
2766 `grep-files-aliases' or file name extension pattern is returned."
2767   (when buffer-file-name
2768     (let* ((fn (file-name-nondirectory buffer-file-name))
2769            (default-alias
2770              (let ((aliases (remove (assoc "all" grep-files-aliases)
2771                                     grep-files-aliases))
2772                    alias)
2773                (while aliases
2774                  (setq alias (car aliases)
2775                        aliases (cdr aliases))
2776                  (if (string-match (mapconcat
2777                                     #'wildcard-to-regexp
2778                                     (split-string (cdr alias) nil t)
2779                                     "\\|")
2780                                    fn)
2781                      (setq aliases nil)
2782                    (setq alias nil)))
2783                (cdr alias)))
2784            (default-extension
2785              (let ((ext (file-name-extension fn)))
2786                (and ext (concat "*." ext)))))
2787       (or default-alias default-extension))))
2788
2789 (defun projectile--globally-ignored-file-suffixes-glob ()
2790   "Return ignored file suffixes as a list of glob patterns."
2791   (mapcar (lambda (pat) (concat "*" pat)) projectile-globally-ignored-file-suffixes))
2792
2793 (defun projectile--read-search-string-with-default (prefix-label)
2794   (let* ((prefix-label (projectile-prepend-project-name prefix-label))
2795          (default-value (projectile-symbol-or-selection-at-point))
2796          (default-label (if (or (not default-value)
2797                                 (string= default-value ""))
2798                             ""
2799                           (format " (default %s)" default-value))))
2800     (read-string (format "%s%s: " prefix-label default-label) nil nil default-value)))
2801
2802 ;;;###autoload
2803 (defun projectile-grep (&optional regexp arg)
2804   "Perform rgrep in the project.
2805
2806 With a prefix ARG asks for files (globbing-aware) which to grep in.
2807 With prefix ARG of `-' (such as `M--'), default the files (without prompt),
2808 to `projectile-grep-default-files'.
2809
2810 With REGEXP given, don't query the user for a regexp."
2811   (interactive "i\nP")
2812   (require 'grep) ;; for `rgrep'
2813   (let* ((roots (projectile-get-project-directories (projectile-project-root)))
2814          (search-regexp (or regexp
2815                             (projectile--read-search-string-with-default "Grep for")))
2816          (files (and arg (or (and (equal current-prefix-arg '-)
2817                                   (projectile-grep-default-files))
2818                              (read-string (projectile-prepend-project-name "Grep in: ")
2819                                           (projectile-grep-default-files))))))
2820     (dolist (root-dir roots)
2821       (require 'vc-git) ;; for `vc-git-grep'
2822       ;; in git projects users have the option to use `vc-git-grep' instead of `rgrep'
2823       (if (and (eq (projectile-project-vcs) 'git)
2824                projectile-use-git-grep
2825                (fboundp 'vc-git-grep))
2826           (vc-git-grep search-regexp (or files "") root-dir)
2827         ;; paths for find-grep should relative and without trailing /
2828         (let ((grep-find-ignored-directories
2829                (cl-union (mapcar (lambda (f) (directory-file-name (file-relative-name f root-dir)))
2830                                  (projectile-ignored-directories))
2831                          grep-find-ignored-directories))
2832               (grep-find-ignored-files
2833                (cl-union (append (mapcar (lambda (file)
2834                                            (file-relative-name file root-dir))
2835                                          (projectile-ignored-files))
2836                                  (projectile--globally-ignored-file-suffixes-glob))
2837                          grep-find-ignored-files)))
2838           (grep-compute-defaults)
2839           (rgrep search-regexp (or files "* .*") root-dir))))
2840     (run-hooks 'projectile-grep-finished-hook)))
2841
2842 ;;;###autoload
2843 (defun projectile-ag (search-term &optional arg)
2844   "Run an ag search with SEARCH-TERM in the project.
2845
2846 With an optional prefix argument ARG SEARCH-TERM is interpreted as a
2847 regular expression."
2848   (interactive
2849    (list (projectile--read-search-string-with-default
2850           (format "Ag %ssearch for" (if current-prefix-arg "regexp " "")))
2851          current-prefix-arg))
2852   (if (require 'ag nil 'noerror)
2853       (let ((ag-command (if arg 'ag-regexp 'ag))
2854             (ag-ignore-list (delq nil
2855                                   (delete-dups
2856                                    (append
2857                                     ag-ignore-list
2858                                     (projectile--globally-ignored-file-suffixes-glob)
2859                                     ;; ag supports git ignore files directly
2860                                     (unless (eq (projectile-project-vcs) 'git)
2861                                       (append (projectile-ignored-files-rel)
2862                                               (projectile-ignored-directories-rel)
2863                                               grep-find-ignored-files
2864                                               grep-find-ignored-directories
2865                                               '()))))))
2866             ;; reset the prefix arg, otherwise it will affect the ag-command
2867             (current-prefix-arg nil))
2868         (funcall ag-command search-term (projectile-project-root)))
2869     (error "Package 'ag' is not available")))
2870
2871 ;;;###autoload
2872 (defun projectile-ripgrep (search-term)
2873   "Run a Ripgrep search with `SEARCH-TERM' at current project root.
2874
2875 SEARCH-TERM is a regexp."
2876   (interactive (list (projectile--read-search-string-with-default
2877                       "Ripgrep search for")))
2878   (if (require 'ripgrep nil 'noerror)
2879       (let ((args (mapcar (lambda (val) (concat "--glob !" val))
2880                           (append projectile-globally-ignored-files
2881                                   projectile-globally-ignored-directories))))
2882         (ripgrep-regexp search-term
2883                         (projectile-project-root)
2884                         (if current-prefix-arg
2885                             args
2886                           (cons "--fixed-strings" args))))
2887     (error "Package `ripgrep' is not available")))
2888
2889 (defun projectile-tags-exclude-patterns ()
2890   "Return a string with exclude patterns for ctags."
2891   (mapconcat (lambda (pattern) (format "--exclude=\"%s\""
2892                                        (directory-file-name pattern)))
2893              (projectile-ignored-directories-rel) " "))
2894
2895 ;;;###autoload
2896 (defun projectile-regenerate-tags ()
2897   "Regenerate the project's [e|g]tags."
2898   (interactive)
2899   (if (and (boundp 'ggtags-mode)
2900            (memq projectile-tags-backend '(auto ggtags)))
2901       (progn
2902         (let* ((ggtags-project-root (projectile-project-root))
2903                (default-directory ggtags-project-root))
2904           (ggtags-ensure-project)
2905           (ggtags-update-tags t)))
2906     (let* ((project-root (projectile-project-root))
2907            (tags-exclude (projectile-tags-exclude-patterns))
2908            (default-directory project-root)
2909            (tags-file (expand-file-name projectile-tags-file-name))
2910            (command (format projectile-tags-command tags-file tags-exclude default-directory))
2911            shell-output exit-code)
2912       (with-temp-buffer
2913         (setq exit-code
2914               (call-process-shell-command command nil (current-buffer))
2915               shell-output (string-trim
2916                             (buffer-substring (point-min) (point-max)))))
2917       (unless (zerop exit-code)
2918         (error shell-output))
2919       (visit-tags-table tags-file)
2920       (message "Regenerated %s" tags-file))))
2921
2922 (defun projectile-visit-project-tags-table ()
2923   "Visit the current project's tags table."
2924   (when (projectile-project-p)
2925     (let ((tags-file (projectile-expand-root projectile-tags-file-name)))
2926       (when (file-exists-p tags-file)
2927         (with-demoted-errors "Error loading tags-file: %s"
2928           (visit-tags-table tags-file t))))))
2929
2930 (defun projectile-determine-find-tag-fn ()
2931   "Determine which function to use for a call to `projectile-find-tag'."
2932   (or
2933    (cond
2934     ((eq projectile-tags-backend 'auto)
2935      (cond
2936       ((fboundp 'ggtags-find-tag-dwim)
2937        'ggtags-find-tag-dwim)
2938       ((fboundp 'xref-find-definitions)
2939        'xref-find-definitions)
2940       ((fboundp 'etags-select-find-tag)
2941        'etags-select-find-tag)))
2942     ((eq projectile-tags-backend 'xref)
2943      (when (fboundp 'xref-find-definitions)
2944        'xref-find-definitions))
2945     ((eq projectile-tags-backend 'ggtags)
2946      (when (fboundp 'ggtags-find-tag-dwim)
2947        'ggtags-find-tag-dwim))
2948     ((eq projectile-tags-backend 'etags-select)
2949      (when (fboundp 'etags-select-find-tag)
2950        'etags-select-find-tag)))
2951    'find-tag))
2952
2953 ;;;###autoload
2954 (defun projectile-find-tag ()
2955   "Find tag in project."
2956   (interactive)
2957   (projectile-visit-project-tags-table)
2958   ;; Auto-discover the user's preference for tags
2959   (let ((find-tag-fn (projectile-determine-find-tag-fn)))
2960     (call-interactively find-tag-fn)))
2961
2962 (defmacro projectile-with-default-dir (dir &rest body)
2963   "Invoke in DIR the BODY."
2964   (declare (debug t) (indent 1))
2965   `(let ((default-directory ,dir))
2966      ,@body))
2967
2968 ;;;###autoload
2969 (defun projectile-run-command-in-root ()
2970   "Invoke `execute-extended-command' in the project's root."
2971   (interactive)
2972   (projectile-with-default-dir (projectile-ensure-project (projectile-project-root))
2973     (call-interactively 'execute-extended-command)))
2974
2975 ;;;###autoload
2976 (defun projectile-run-shell-command-in-root ()
2977   "Invoke `shell-command' in the project's root."
2978   (interactive)
2979   (projectile-with-default-dir (projectile-ensure-project (projectile-project-root))
2980     (call-interactively 'shell-command)))
2981
2982 ;;;###autoload
2983 (defun projectile-run-async-shell-command-in-root ()
2984   "Invoke `async-shell-command' in the project's root."
2985   (interactive)
2986   (projectile-with-default-dir (projectile-ensure-project (projectile-project-root))
2987     (call-interactively 'async-shell-command)))
2988
2989 ;;;###autoload
2990 (defun projectile-run-shell ()
2991   "Invoke `shell' in the project's root.
2992
2993 Switch to the project specific shell buffer if it already exists."
2994   (interactive)
2995   (projectile-with-default-dir (projectile-ensure-project (projectile-project-root))
2996     (shell (concat "*shell " (projectile-project-name) "*"))))
2997
2998 ;;;###autoload
2999 (defun projectile-run-eshell ()
3000   "Invoke `eshell' in the project's root.
3001
3002 Switch to the project specific eshell buffer if it already exists."
3003   (interactive)
3004   (projectile-with-default-dir (projectile-ensure-project (projectile-project-root))
3005     (let ((eshell-buffer-name (concat "*eshell " (projectile-project-name) "*")))
3006       (eshell))))
3007
3008 ;;;###autoload
3009 (defun projectile-run-ielm ()
3010   "Invoke `ielm' in the project's root.
3011
3012 Switch to the project specific ielm buffer if it already exists."
3013   (interactive)
3014   (let* ((project (projectile-ensure-project (projectile-project-root)))
3015          (ielm-buffer-name (format "*ielm %s*" (projectile-project-name project))))
3016     (if (get-buffer ielm-buffer-name)
3017         (switch-to-buffer ielm-buffer-name)
3018       (projectile-with-default-dir project
3019         (ielm))
3020       ;; ielm's buffer name is hardcoded, so we have to rename it after creation
3021       (rename-buffer ielm-buffer-name))))
3022
3023 ;;;###autoload
3024 (defun projectile-run-term (program)
3025   "Invoke `term' in the project's root.
3026
3027 Switch to the project specific term buffer if it already exists."
3028   (interactive (list nil))
3029   (let* ((project (projectile-ensure-project (projectile-project-root)))
3030          (term (concat "term " (projectile-project-name project)))
3031          (buffer (concat "*" term "*")))
3032     (unless (get-buffer buffer)
3033       (require 'term)
3034       (let ((program (or program
3035                          (read-from-minibuffer "Run program: "
3036                                                (or explicit-shell-file-name
3037                                                    (getenv "ESHELL")
3038                                                    (getenv "SHELL")
3039                                                    "/bin/sh")))))
3040         (projectile-with-default-dir project
3041           (set-buffer (make-term term program))
3042           (term-mode)
3043           (term-char-mode))))
3044     (switch-to-buffer buffer)))
3045
3046 (defun projectile-files-in-project-directory (directory)
3047   "Return a list of files in DIRECTORY."
3048   (let* ((project (projectile-ensure-project (projectile-project-root)))
3049          (dir (file-relative-name (expand-file-name directory)
3050                                   project)))
3051     (cl-remove-if-not
3052      (lambda (f) (string-prefix-p dir f))
3053      (projectile-project-files project))))
3054
3055 (defun projectile-files-from-cmd (cmd directory)
3056   "Use a grep-like CMD to search for files within DIRECTORY.
3057
3058 CMD should include the necessary search params and should output
3059 equivalently to grep -HlI (only unique matching filenames).
3060 Returns a list of expanded filenames."
3061   (let ((default-directory directory))
3062     (mapcar (lambda (str)
3063               (concat directory
3064                       (if (string-prefix-p "./" str)
3065                           (substring str 2)
3066                         str)))
3067             (split-string
3068              (string-trim (shell-command-to-string cmd))
3069              "\n+"
3070              t))))
3071
3072 (defun projectile-files-with-string (string directory)
3073   "Return a list of all files containing STRING in DIRECTORY.
3074
3075 Tries to use ag, ack, git-grep, and grep in that order.  If those
3076 are impossible (for instance on Windows), returns a list of all
3077 files in the project."
3078   (if (projectile-unixy-system-p)
3079       (let* ((search-term (shell-quote-argument string))
3080              (cmd (cond ((executable-find "ag")
3081                          (concat "ag --literal --nocolor --noheading -l -- "
3082                                  search-term))
3083                         ((executable-find "ack")
3084                          (concat "ack --literal --noheading --nocolor -l -- "
3085                                  search-term))
3086                         ((and (executable-find "git")
3087                               (eq (projectile-project-vcs) 'git))
3088                          (concat "git grep -HlI " search-term))
3089                         (t
3090                          ;; -r: recursive
3091                          ;; -H: show filename for each match
3092                          ;; -l: show only file names with matches
3093                          ;; -I: no binary files
3094                          (format "grep -rHlI %s ." search-term)))))
3095         (projectile-files-from-cmd cmd directory))
3096     ;; we have to reject directories as a workaround to work with git submodules
3097     (cl-remove-if
3098      #'file-directory-p
3099      (mapcar #'projectile-expand-root (projectile-dir-files directory)))))
3100
3101 ;;;###autoload
3102 (defun projectile-replace (&optional arg)
3103   "Replace literal string in project using non-regexp `tags-query-replace'.
3104
3105 With a prefix argument ARG prompts you for a directory on which
3106 to run the replacement."
3107   (interactive "P")
3108   (let* ((directory (if arg
3109                         (file-name-as-directory
3110                          (read-directory-name "Replace in directory: "))
3111                       (projectile-ensure-project (projectile-project-root))))
3112          (old-text (read-string
3113                     (projectile-prepend-project-name "Replace: ")
3114                     (projectile-symbol-or-selection-at-point)))
3115          (new-text (read-string
3116                     (projectile-prepend-project-name
3117                      (format "Replace %s with: " old-text))))
3118          (files (projectile-files-with-string old-text directory)))
3119     ;; Adapted from `tags-query-replace' for literal strings (not regexp)
3120     (setq tags-loop-scan `(let ,(unless (equal old-text (downcase old-text))
3121                                   '((case-fold-search nil)))
3122                             (if (search-forward ',old-text nil t)
3123                                 ;; When we find a match, move back to
3124                                 ;; the beginning of it so
3125                                 ;; perform-replace will see it.
3126                                 (goto-char (match-beginning 0))))
3127           tags-loop-operate `(perform-replace ',old-text ',new-text t nil nil
3128                                               nil multi-query-replace-map))
3129     (tags-loop-continue (or (cons 'list files) t))))
3130
3131 ;;;###autoload
3132 (defun projectile-replace-regexp (&optional arg)
3133   "Replace a regexp in the project using `tags-query-replace'.
3134
3135 With a prefix argument ARG prompts you for a directory on which
3136 to run the replacement."
3137   (interactive "P")
3138   (let* ((directory (if arg
3139                         (file-name-as-directory
3140                          (read-directory-name "Replace regexp in directory: "))
3141                       (projectile-ensure-project (projectile-project-root))))
3142          (old-text (read-string
3143                     (projectile-prepend-project-name "Replace regexp: ")
3144                     (projectile-symbol-or-selection-at-point)))
3145          (new-text (read-string
3146                     (projectile-prepend-project-name
3147                      (format "Replace regexp %s with: " old-text))))
3148          (files
3149           ;; We have to reject directories as a workaround to work with git submodules.
3150           ;;
3151           ;; We can't narrow the list of files with
3152           ;; `projectile-files-with-string' because those regexp tools
3153           ;; don't support Emacs regular expressions.
3154           (cl-remove-if
3155            #'file-directory-p
3156            (mapcar #'projectile-expand-root (projectile-dir-files directory)))))
3157     (tags-query-replace old-text new-text nil (cons 'list files))))
3158
3159 ;;;###autoload
3160 (defun projectile-kill-buffers ()
3161   "Kill project buffers.
3162
3163 The buffer are killed according to the value of
3164 `projectile-kill-buffers-filter'."
3165   (interactive)
3166   (let* ((project (projectile-ensure-project (projectile-project-root)))
3167          (project-name (projectile-project-name project))
3168          (buffers (projectile-project-buffers project)))
3169     (when (yes-or-no-p
3170            (format "Are you sure you want to kill %s buffers for '%s'? "
3171                    (length buffers) project-name))
3172       (dolist (buffer buffers)
3173         (when (and
3174                ;; we take care not to kill indirect buffers directly
3175                ;; as we might encounter them after their base buffers are killed
3176                (not (buffer-base-buffer buffer))
3177                (if (functionp projectile-kill-buffers-filter)
3178                    (funcall projectile-kill-buffers-filter buffer)
3179                  (pcase projectile-kill-buffers-filter
3180                    ('kill-all t)
3181                    ('kill-only-files (buffer-file-name buffer))
3182                    (_ (user-error "Invalid projectile-kill-buffers-filter value: %S" projectile-kill-buffers-filter)))))
3183           (kill-buffer buffer))))))
3184
3185 ;;;###autoload
3186 (defun projectile-save-project-buffers ()
3187   "Save all project buffers."
3188   (interactive)
3189   (let* ((project (projectile-ensure-project (projectile-project-root)))
3190          (project-name (projectile-project-name project))
3191          (modified-buffers (cl-remove-if-not (lambda (buf)
3192                                                (and (buffer-file-name buf)
3193                                                     (buffer-modified-p buf)))
3194                                              (projectile-project-buffers project))))
3195     (if (null modified-buffers)
3196         (message "[%s] No buffers need saving" project-name)
3197       (dolist (buf modified-buffers)
3198         (with-current-buffer buf
3199           (save-buffer)))
3200       (message "[%s] Saved %d buffers" project-name (length modified-buffers)))))
3201
3202 ;;;###autoload
3203 (defun projectile-dired ()
3204   "Open `dired' at the root of the project."
3205   (interactive)
3206   (dired (projectile-ensure-project (projectile-project-root))))
3207
3208 ;;;###autoload
3209 (defun projectile-dired-other-window ()
3210   "Open `dired'  at the root of the project in another window."
3211   (interactive)
3212   (dired-other-window (projectile-ensure-project (projectile-project-root))))
3213
3214 ;;;###autoload
3215 (defun projectile-dired-other-frame ()
3216   "Open `dired' at the root of the project in another frame."
3217   (interactive)
3218   (dired-other-frame (projectile-ensure-project (projectile-project-root))))
3219
3220 ;;;###autoload
3221 (defun projectile-vc (&optional project-root)
3222   "Open `vc-dir' at the root of the project.
3223
3224 For git projects `magit-status-internal' is used if available.
3225 For hg projects `monky-status' is used if available.
3226
3227 If PROJECT-ROOT is given, it is opened instead of the project
3228 root directory of the current buffer file.  If interactively
3229 called with a prefix argument, the user is prompted for a project
3230 directory to open."
3231   (interactive (and current-prefix-arg
3232                     (list
3233                      (projectile-completing-read
3234                       "Open project VC in: "
3235                       projectile-known-projects))))
3236   (or project-root (setq project-root (projectile-project-root)))
3237   (let ((vcs (projectile-project-vcs project-root)))
3238     (cl-case vcs
3239       (git
3240        (cond ((fboundp 'magit-status-internal)
3241               (magit-status-internal project-root))
3242              ((fboundp 'magit-status)
3243               (with-no-warnings (magit-status project-root)))
3244              (t
3245               (vc-dir project-root))))
3246       (hg
3247        (if (fboundp 'monky-status)
3248            (monky-status project-root)
3249          (vc-dir project-root)))
3250       (t (vc-dir project-root)))))
3251
3252 ;;;###autoload
3253 (defun projectile-recentf ()
3254   "Show a list of recently visited files in a project."
3255   (interactive)
3256   (if (boundp 'recentf-list)
3257       (find-file (projectile-expand-root
3258                   (projectile-completing-read
3259                    "Recently visited files: "
3260                    (projectile-recentf-files))))
3261     (message "recentf is not enabled")))
3262
3263 (defun projectile-recentf-files ()
3264   "Return a list of recently visited files in a project."
3265   (and (boundp 'recentf-list)
3266        (let ((project-root (projectile-ensure-project (projectile-project-root))))
3267          (mapcar
3268           (lambda (f) (file-relative-name f project-root))
3269           (cl-remove-if-not
3270            (lambda (f) (string-prefix-p project-root f))
3271            recentf-list)))))
3272
3273 (defun projectile-serialize-cache ()
3274   "Serializes the memory cache to the hard drive."
3275   (projectile-serialize projectile-projects-cache projectile-cache-file))
3276
3277 (defvar projectile-configure-cmd-map
3278   (make-hash-table :test 'equal)
3279   "A mapping between projects and the last configure command used on them.")
3280
3281 (defvar projectile-compilation-cmd-map
3282   (make-hash-table :test 'equal)
3283   "A mapping between projects and the last compilation command used on them.")
3284
3285 (defvar projectile-test-cmd-map
3286   (make-hash-table :test 'equal)
3287   "A mapping between projects and the last test command used on them.")
3288
3289 (defvar projectile-run-cmd-map
3290   (make-hash-table :test 'equal)
3291   "A mapping between projects and the last run command used on them.")
3292
3293 (defvar projectile-project-configure-cmd nil
3294   "The command to use with `projectile-configure-project'.
3295 It takes precedence over the default command for the project type when set.
3296 Should be set via .dir-locals.el.")
3297
3298 (defvar projectile-project-compilation-cmd nil
3299   "The command to use with `projectile-compile-project'.
3300 It takes precedence over the default command for the project type when set.
3301 Should be set via .dir-locals.el.")
3302
3303 (defvar projectile-project-compilation-dir nil
3304   "The directory to use with `projectile-compile-project'.
3305 The directory path is relative to the project root.
3306 Should be set via .dir-locals.el.")
3307
3308 (defvar projectile-project-test-cmd nil
3309   "The command to use with `projectile-test-project'.
3310 It takes precedence over the default command for the project type when set.
3311 Should be set via .dir-locals.el.")
3312
3313 (defvar projectile-project-run-cmd nil
3314   "The command to use with `projectile-run-project'.
3315 It takes precedence over the default command for the project type when set.
3316 Should be set via .dir-locals.el.")
3317
3318 (defun projectile-default-generic-command (project-type command-type)
3319   "Generic retrieval of COMMAND-TYPEs default cmd-value for PROJECT-TYPE.
3320
3321 If found, checks if value is symbol or string.  In case of symbol
3322 resolves to function `funcall's.  Return value of function MUST
3323 be string to be executed as command."
3324   (let ((command (plist-get (alist-get project-type projectile-project-types) command-type)))
3325     (cond
3326      ((stringp command) command)
3327      ((functionp command)
3328       (if (fboundp command)
3329           (funcall (symbol-function command))))
3330      ((and (not command) (eq command-type 'compilation-dir))
3331       ;; `compilation-dir' is special in that it is used as a fallback for the root
3332       nil)
3333      (t
3334       (user-error "The value for: %s in project-type: %s was neither a function nor a string." command-type project-type)))))
3335
3336 (defun projectile-default-configure-command (project-type)
3337   "Retrieve default configure command for PROJECT-TYPE."
3338   (projectile-default-generic-command project-type 'configure-command))
3339
3340 (defun projectile-default-compilation-command (project-type)
3341   "Retrieve default compilation command for PROJECT-TYPE."
3342   (projectile-default-generic-command project-type 'compile-command))
3343
3344 (defun projectile-default-compilation-dir (project-type)
3345   "Retrieve default compilation directory for PROJECT-TYPE."
3346   (projectile-default-generic-command project-type 'compilation-dir))
3347
3348 (defun projectile-default-test-command (project-type)
3349   "Retrieve default test command for PROJECT-TYPE."
3350   (projectile-default-generic-command project-type 'test-command))
3351
3352 (defun projectile-default-run-command (project-type)
3353   "Retrieve default run command for PROJECT-TYPE."
3354   (projectile-default-generic-command project-type 'run-command))
3355
3356 (defun projectile-configure-command (compile-dir)
3357   "Retrieve the configure command for COMPILE-DIR.
3358
3359 The command is determined like this:
3360
3361 - first we check `projectile-configure-cmd-map' for the last
3362 configure command that was invoked on the project
3363
3364 - then we check for `projectile-project-configure-cmd' supplied
3365 via .dir-locals.el
3366
3367 - finally we check for the default configure command for a
3368 project of that type"
3369   (or (gethash compile-dir projectile-configure-cmd-map)
3370       projectile-project-configure-cmd
3371       (let ((cmd-format-string (projectile-default-configure-command (projectile-project-type))))
3372         (when cmd-format-string
3373           (format cmd-format-string (projectile-project-root))))))
3374
3375 (defun projectile-compilation-command (compile-dir)
3376   "Retrieve the compilation command for COMPILE-DIR.
3377
3378 The command is determined like this:
3379
3380 - first we check `projectile-compilation-cmd-map' for the last
3381 compile command that was invoked on the project
3382
3383 - then we check for `projectile-project-compilation-cmd' supplied
3384 via .dir-locals.el
3385
3386 - finally we check for the default compilation command for a
3387 project of that type"
3388   (or (gethash compile-dir projectile-compilation-cmd-map)
3389       projectile-project-compilation-cmd
3390       (projectile-default-compilation-command (projectile-project-type))))
3391
3392 (defun projectile-test-command (compile-dir)
3393   "Retrieve the test command for COMPILE-DIR.
3394
3395 The command is determined like this:
3396
3397 - first we check `projectile-test-cmd-map' for the last
3398 test command that was invoked on the project
3399
3400 - then we check for `projectile-project-test-cmd' supplied
3401 via .dir-locals.el
3402
3403 - finally we check for the default test command for a
3404 project of that type"
3405   (or (gethash compile-dir projectile-test-cmd-map)
3406       projectile-project-test-cmd
3407       (projectile-default-test-command (projectile-project-type))))
3408
3409 (defun projectile-run-command (compile-dir)
3410   "Retrieve the run command for COMPILE-DIR.
3411
3412 The command is determined like this:
3413
3414 - first we check `projectile-run-cmd-map' for the last
3415 run command that was invoked on the project
3416
3417 - then we check for `projectile-project-run-cmd' supplied
3418 via .dir-locals.el
3419
3420 - finally we check for the default run command for a
3421 project of that type"
3422   (or (gethash compile-dir projectile-run-cmd-map)
3423       projectile-project-run-cmd
3424       (projectile-default-run-command (projectile-project-type))))
3425
3426 (defun projectile-read-command (prompt command)
3427   "Adapted from `compilation-read-command'."
3428   (read-shell-command prompt command
3429                       (if (equal (car compile-history) command)
3430                           '(compile-history . 1)
3431                         'compile-history)))
3432
3433 (defun projectile-compilation-dir ()
3434   "Retrieve the compilation directory for this project."
3435   (let* ((type (projectile-project-type))
3436          (directory (or projectile-project-compilation-dir
3437                         (projectile-default-compilation-dir type))))
3438     (if directory
3439         (file-truename
3440          (concat (file-name-as-directory (projectile-project-root))
3441                  (file-name-as-directory directory)))
3442       (projectile-project-root))))
3443
3444 (defun projectile-maybe-read-command (arg default-cmd prompt)
3445   "Prompt user for command unless DEFAULT-CMD is an Elisp function."
3446   (if (and (or (stringp default-cmd) (null default-cmd))
3447            (or compilation-read-command arg))
3448       (projectile-read-command prompt default-cmd)
3449     default-cmd))
3450
3451 (defun projectile-run-compilation (cmd)
3452   "Run external or Elisp compilation command CMD."
3453   (if (functionp cmd)
3454       (funcall cmd)
3455     (compile cmd)))
3456
3457 (defvar projectile-project-command-history (make-hash-table :test 'equal)
3458   "The history of last executed project commands, per project.
3459
3460 Projects are indexed by their project-root value.")
3461
3462 (defun projectile--get-command-history (project-root)
3463   (or (gethash project-root projectile-project-command-history)
3464       (puthash project-root
3465                (make-ring 16)
3466                projectile-project-command-history)))
3467
3468 (cl-defun projectile--run-project-cmd
3469     (command command-map &key show-prompt prompt-prefix save-buffers)
3470   "Run a project COMMAND, typically a test- or compile command.
3471
3472 Cache the COMMAND for later use inside the hash-table COMMAND-MAP.
3473
3474 Normally you'll be prompted for a compilation command, unless
3475 variable `compilation-read-command'.  You can force the prompt
3476 by setting SHOW-PROMPT.  The prompt will be prefixed with PROMPT-PREFIX.
3477
3478 If SAVE-BUFFERS is non-nil save all projectile buffers before
3479 running the command.
3480
3481 The command actually run is returned."
3482   (let* ((project-root (projectile-project-root))
3483          (default-directory (projectile-compilation-dir))
3484          (command (projectile-maybe-read-command show-prompt
3485                                                  command
3486                                                  prompt-prefix)))
3487     (when command-map
3488       (puthash default-directory command command-map)
3489       (ring-insert (projectile--get-command-history project-root) command))
3490     (when save-buffers
3491       (save-some-buffers (not compilation-ask-about-save)
3492                          (lambda ()
3493                            (projectile-project-buffer-p (current-buffer)
3494                                                         project-root))))
3495     (unless (file-directory-p default-directory)
3496       (mkdir default-directory))
3497     (projectile-run-compilation command)
3498     command))
3499
3500 ;;;###autoload
3501 (defun projectile-configure-project (arg)
3502   "Run project configure command.
3503
3504 Normally you'll be prompted for a compilation command, unless
3505 variable `compilation-read-command'.  You can force the prompt
3506 with a prefix ARG."
3507   (interactive "P")
3508   (let ((command (projectile-configure-command (projectile-compilation-dir))))
3509     (projectile--run-project-cmd command projectile-configure-cmd-map
3510                                  :show-prompt arg
3511                                  :prompt-prefix "Configure command: "
3512                                  :save-buffers t)))
3513
3514 ;;;###autoload
3515 (defun projectile-compile-project (arg)
3516   "Run project compilation command.
3517
3518 Normally you'll be prompted for a compilation command, unless
3519 variable `compilation-read-command'.  You can force the prompt
3520 with a prefix ARG."
3521   (interactive "P")
3522   (let ((command (projectile-compilation-command (projectile-compilation-dir))))
3523     (projectile--run-project-cmd command projectile-compilation-cmd-map
3524                                  :show-prompt arg
3525                                  :prompt-prefix "Compile command: "
3526                                  :save-buffers t)))
3527
3528 ;;;###autoload
3529 (defun projectile-test-project (arg)
3530   "Run project test command.
3531
3532 Normally you'll be prompted for a compilation command, unless
3533 variable `compilation-read-command'.  You can force the prompt
3534 with a prefix ARG."
3535   (interactive "P")
3536   (let ((command (projectile-test-command (projectile-compilation-dir))))
3537     (projectile--run-project-cmd command projectile-test-cmd-map
3538                                  :show-prompt arg
3539                                  :prompt-prefix "Test command: "
3540                                  :save-buffers t)))
3541
3542 ;;;###autoload
3543 (defun projectile-run-project (arg)
3544   "Run project run command.
3545
3546 Normally you'll be prompted for a compilation command, unless
3547 variable `compilation-read-command'.  You can force the prompt
3548 with a prefix ARG."
3549   (interactive "P")
3550   (let ((command (projectile-run-command (projectile-compilation-dir))))
3551     (projectile--run-project-cmd command projectile-run-cmd-map
3552                                  :show-prompt arg
3553                                  :prompt-prefix "Run command: ")))
3554
3555 ;;;###autoload
3556 (defun projectile-repeat-last-command (show-prompt)
3557   "Run last projectile external command.
3558
3559 External commands are: `projectile-configure-project',
3560 `projectile-compile-project', `projectile-test-project' and
3561 `projectile-run-project'.
3562
3563 If the prefix argument SHOW_PROMPT is non nil, the command can be edited."
3564   (interactive "P")
3565   (let* ((project-root
3566           (projectile-ensure-project (projectile-project-root)))
3567          (command-history (projectile--get-command-history project-root))
3568          (command (car-safe (ring-elements command-history)))
3569          (compilation-read-command show-prompt)
3570          executed-command)
3571     (unless command
3572       (user-error "No command has been run yet for this project"))
3573     (setq executed-command
3574           (projectile--run-project-cmd command
3575                                        nil
3576                                        :save-buffers t
3577                                        :prompt-prefix "Execute command: "))
3578     (unless (string= command executed-command)
3579       (ring-insert command-history executed-command))))
3580
3581 (defadvice compilation-find-file (around projectile-compilation-find-file)
3582   "Try to find a buffer for FILENAME, if we cannot find it,
3583 fallback to the original function."
3584   (let ((filename (ad-get-arg 1))
3585         full-filename)
3586     (ad-set-arg 1
3587                 (or
3588                  (if (file-exists-p (expand-file-name filename))
3589                      filename)
3590                  ;; Try to find the filename using projectile
3591                  (and (projectile-project-p)
3592                       (let ((root (projectile-project-root))
3593                             (dirs (cons "" (projectile-current-project-dirs))))
3594                         (when (setq full-filename
3595                                     (car (cl-remove-if-not
3596                                           #'file-exists-p
3597                                           (mapcar
3598                                            (lambda (f)
3599                                              (expand-file-name
3600                                               filename
3601                                               (expand-file-name f root)))
3602                                            dirs))))
3603                           full-filename)))
3604                  ;; Fall back to the old argument
3605                  filename))
3606     ad-do-it))
3607
3608 (defun projectile-open-projects ()
3609   "Return a list of all open projects.
3610 An open project is a project with any open buffers."
3611   (delete-dups
3612    (delq nil
3613          (mapcar (lambda (buffer)
3614                    (with-current-buffer buffer
3615                      (when (projectile-project-p)
3616                        (abbreviate-file-name (projectile-project-root)))))
3617                  (buffer-list)))))
3618
3619 (defun projectile--remove-current-project (projects)
3620   "Remove the current project (if any) from the list of PROJECTS."
3621   (if-let ((project (projectile-project-root)))
3622       (projectile-difference projects
3623                              (list (abbreviate-file-name project)))
3624     projects))
3625
3626 (defun projectile--move-current-project-to-end (projects)
3627   "Move current project (if any) to the end of list in the list of PROJECTS."
3628   (if-let ((project (projectile-project-root)))
3629       (append
3630        (projectile--remove-current-project projects)
3631        (list (abbreviate-file-name project)))
3632     projects))
3633
3634 (defun projectile-relevant-known-projects ()
3635   "Return a list of known projects."
3636   (pcase projectile-current-project-on-switch
3637    ('remove (projectile--remove-current-project projectile-known-projects))
3638    ('move-to-end (projectile--move-current-project-to-end projectile-known-projects))
3639    ('keep projectile-known-projects)))
3640
3641 (defun projectile-relevant-open-projects ()
3642   "Return a list of open projects."
3643   (let ((open-projects (projectile-open-projects)))
3644     (pcase projectile-current-project-on-switch
3645      ('remove (projectile--remove-current-project open-projects))
3646      ('move-to-end (projectile--move-current-project-to-end open-projects))
3647      ('keep open-projects))))
3648
3649 ;;;###autoload
3650 (defun projectile-switch-project (&optional arg)
3651   "Switch to a project we have visited before.
3652 Invokes the command referenced by `projectile-switch-project-action' on switch.
3653 With a prefix ARG invokes `projectile-commander' instead of
3654 `projectile-switch-project-action.'"
3655   (interactive "P")
3656   (let ((projects (projectile-relevant-known-projects)))
3657     (if projects
3658         (projectile-completing-read
3659          "Switch to project: " projects
3660          :action (lambda (project)
3661                    (projectile-switch-project-by-name project arg)))
3662       (user-error "There are no known projects"))))
3663
3664 ;;;###autoload
3665 (defun projectile-switch-open-project (&optional arg)
3666   "Switch to a project we have currently opened.
3667 Invokes the command referenced by `projectile-switch-project-action' on switch.
3668 With a prefix ARG invokes `projectile-commander' instead of
3669 `projectile-switch-project-action.'"
3670   (interactive "P")
3671   (let ((projects (projectile-relevant-open-projects)))
3672     (if projects
3673         (projectile-completing-read
3674          "Switch to open project: " projects
3675          :action (lambda (project)
3676                    (projectile-switch-project-by-name project arg)))
3677       (user-error "There are no open projects"))))
3678
3679 (defun projectile-switch-project-by-name (project-to-switch &optional arg)
3680   "Switch to project by project name PROJECT-TO-SWITCH.
3681 Invokes the command referenced by `projectile-switch-project-action' on switch.
3682 With a prefix ARG invokes `projectile-commander' instead of
3683 `projectile-switch-project-action.'"
3684   (unless (projectile-project-p project-to-switch)
3685     (projectile-remove-known-project project-to-switch)
3686     (error "Directory %s is not a project" project-to-switch))
3687   (let ((switch-project-action (if arg
3688                                    'projectile-commander
3689                                  projectile-switch-project-action)))
3690     (run-hooks 'projectile-before-switch-project-hook)
3691     (let ((default-directory project-to-switch))
3692       ;; use a temporary buffer to load PROJECT-TO-SWITCH's dir-locals before calling SWITCH-PROJECT-ACTION
3693       (with-temp-buffer
3694         (hack-dir-local-variables-non-file-buffer))
3695       ;; Normally the project name is determined from the current
3696       ;; buffer. However, when we're switching projects, we want to
3697       ;; show the name of the project being switched to, rather than
3698       ;; the current project, in the minibuffer. This is a simple hack
3699       ;; to tell the `projectile-project-name' function to ignore the
3700       ;; current buffer and the caching mechanism, and just return the
3701       ;; value of the `projectile-project-name' variable.
3702       (let ((projectile-project-name (funcall projectile-project-name-function
3703                                               project-to-switch)))
3704         (funcall switch-project-action)))
3705     (run-hooks 'projectile-after-switch-project-hook)))
3706
3707 ;;;###autoload
3708 (defun projectile-find-file-in-directory (&optional directory)
3709   "Jump to a file in a (maybe regular) DIRECTORY.
3710
3711 This command will first prompt for the directory the file is in."
3712   (interactive "DFind file in directory: ")
3713   (unless (projectile--directory-p directory)
3714     (user-error "Directory %S does not exist" directory))
3715   (let ((default-directory directory))
3716     (if (projectile-project-p)
3717         ;; target directory is in a project
3718         (let ((file (projectile-completing-read "Find file: "
3719                                                 (projectile-dir-files directory))))
3720           (find-file (expand-file-name file (projectile-project-root)))
3721           (run-hooks 'projectile-find-file-hook))
3722       ;; target directory is not in a project
3723       (projectile-find-file))))
3724
3725 (defun projectile-all-project-files ()
3726   "Get a list of all files in all projects."
3727   (cl-mapcan
3728    (lambda (project)
3729      (when (file-exists-p project)
3730        (mapcar (lambda (file)
3731                  (expand-file-name file project))
3732                (projectile-project-files project))))
3733    projectile-known-projects))
3734
3735 ;;;###autoload
3736 (defun projectile-find-file-in-known-projects ()
3737   "Jump to a file in any of the known projects."
3738   (interactive)
3739   (find-file (projectile-completing-read "Find file in projects: " (projectile-all-project-files))))
3740
3741 (defun projectile-keep-project-p (project)
3742   "Determine whether we should cleanup (remove) PROJECT or not.
3743
3744 It handles the case of remote projects as well.
3745 See `projectile--cleanup-known-projects'."
3746   ;; Taken from from `recentf-keep-default-predicate'
3747   (cond
3748    ((file-remote-p project nil t) (file-readable-p project))
3749    ((file-remote-p project))
3750    ((file-readable-p project))))
3751
3752 (defun projectile--cleanup-known-projects ()
3753   "Remove known projects that don't exist anymore and return a list of projects removed."
3754   (projectile-merge-known-projects)
3755   (let ((projects-kept (cl-remove-if-not #'projectile-keep-project-p projectile-known-projects))
3756         (projects-removed (cl-remove-if #'projectile-keep-project-p projectile-known-projects)))
3757     (setq projectile-known-projects projects-kept)
3758     (projectile-merge-known-projects)
3759     projects-removed))
3760
3761 ;;;###autoload
3762 (defun projectile-cleanup-known-projects ()
3763   "Remove known projects that don't exist anymore."
3764   (interactive)
3765   (if-let ((projects-removed (projectile--cleanup-known-projects)))
3766       (message "Projects removed: %s"
3767                (mapconcat #'identity projects-removed ", "))
3768     (message "No projects needed to be removed.")))
3769
3770 ;;;###autoload
3771 (defun projectile-clear-known-projects ()
3772   "Clear both `projectile-known-projects' and `projectile-known-projects-file'."
3773   (interactive)
3774   (setq projectile-known-projects nil)
3775   (projectile-save-known-projects))
3776
3777 ;;;###autoload
3778 (defun projectile-remove-known-project (&optional project)
3779   "Remove PROJECT from the list of known projects."
3780   (interactive (list (projectile-completing-read
3781                       "Remove from known projects: " projectile-known-projects
3782                       :action 'projectile-remove-known-project)))
3783   (unless (called-interactively-p 'any)
3784     (setq projectile-known-projects
3785           (cl-remove-if
3786            (lambda (proj) (string= project proj))
3787            projectile-known-projects))
3788     (projectile-merge-known-projects)
3789     (when projectile-verbose
3790       (message "Project %s removed from the list of known projects." project))))
3791
3792 ;;;###autoload
3793 (defun projectile-remove-current-project-from-known-projects ()
3794   "Remove the current project from the list of known projects."
3795   (interactive)
3796   (projectile-remove-known-project (abbreviate-file-name (projectile-project-root))))
3797
3798 (defun projectile-ignored-projects ()
3799   "A list of projects that should not be save in `projectile-known-projects'."
3800   (mapcar #'file-truename projectile-ignored-projects))
3801
3802 (defun projectile-ignored-project-p (project-root)
3803   "Return t if PROJECT-ROOT should not be added to `projectile-known-projects'."
3804   (or (member project-root (projectile-ignored-projects))
3805       (and (functionp projectile-ignored-project-function)
3806            (funcall projectile-ignored-project-function project-root))))
3807
3808 (defun projectile-add-known-project (project-root)
3809   "Add PROJECT-ROOT to the list of known projects."
3810   (interactive (list (read-directory-name "Add to known projects: ")))
3811   (unless (projectile-ignored-project-p project-root)
3812     (setq projectile-known-projects
3813           (delete-dups
3814            (cons (file-name-as-directory (abbreviate-file-name project-root))
3815                  projectile-known-projects)))
3816     (projectile-merge-known-projects)))
3817
3818 (defun projectile-load-known-projects ()
3819   "Load saved projects from `projectile-known-projects-file'.
3820 Also set `projectile-known-projects'."
3821   (setq projectile-known-projects
3822         (projectile-unserialize projectile-known-projects-file))
3823   (setq projectile-known-projects-on-file
3824         (and (sequencep projectile-known-projects)
3825              (copy-sequence projectile-known-projects))))
3826
3827 (defun projectile-save-known-projects ()
3828   "Save PROJECTILE-KNOWN-PROJECTS to PROJECTILE-KNOWN-PROJECTS-FILE."
3829   (projectile-serialize projectile-known-projects
3830                         projectile-known-projects-file)
3831   (setq projectile-known-projects-on-file
3832         (and (sequencep projectile-known-projects)
3833              (copy-sequence projectile-known-projects))))
3834
3835 (defun projectile-merge-known-projects ()
3836   "Merge any change from `projectile-known-projects-file' and save to disk.
3837
3838 This enables multiple Emacs processes to make changes without
3839 overwriting each other's changes."
3840   (let* ((known-now projectile-known-projects)
3841          (known-on-last-sync projectile-known-projects-on-file)
3842          (known-on-file
3843           (projectile-unserialize projectile-known-projects-file))
3844          (removed-after-sync (projectile-difference known-on-last-sync known-now))
3845          (removed-in-other-process
3846           (projectile-difference known-on-last-sync known-on-file))
3847          (result (delete-dups
3848                   (projectile-difference
3849                    (append known-now known-on-file)
3850                    (append removed-after-sync removed-in-other-process)))))
3851     (setq projectile-known-projects result)
3852     (projectile-save-known-projects)))
3853
3854
3855 ;;; IBuffer integration
3856 (define-ibuffer-filter projectile-files
3857     "Show Ibuffer with all buffers in the current project."
3858   (:reader (read-directory-name "Project root: " (projectile-project-root))
3859            :description nil)
3860   (with-current-buffer buf
3861     (equal (file-name-as-directory (expand-file-name qualifier))
3862            (projectile-project-root))))
3863
3864 (defun projectile-ibuffer-by-project (project-root)
3865   "Open an IBuffer window showing all buffers in PROJECT-ROOT."
3866   (let ((project-name (funcall projectile-project-name-function project-root)))
3867     (ibuffer nil (format "*%s Buffers*" project-name)
3868              (list (cons 'projectile-files project-root)))))
3869
3870 ;;;###autoload
3871 (defun projectile-ibuffer (prompt-for-project)
3872   "Open an IBuffer window showing all buffers in the current project.
3873
3874 Let user choose another project when PROMPT-FOR-PROJECT is supplied."
3875   (interactive "P")
3876   (let ((project-root (if prompt-for-project
3877                           (projectile-completing-read
3878                            "Project name: "
3879                            (projectile-relevant-known-projects))
3880                         (projectile-project-root))))
3881
3882     (projectile-ibuffer-by-project project-root)))
3883
3884
3885 ;;;; projectile-commander
3886
3887 (defconst projectile-commander-help-buffer "*Projectile Commander Help*")
3888
3889 (defvar projectile-commander-methods nil
3890   "List of file-selection methods for the `projectile-commander' command.
3891 Each element is a list (KEY DESCRIPTION FUNCTION).
3892 DESCRIPTION is a one-line description of what the key selects.")
3893
3894 ;;;###autoload
3895 (defun projectile-commander ()
3896   "Execute a Projectile command with a single letter.
3897 The user is prompted for a single character indicating the action to invoke.
3898 The `?' character describes then
3899 available actions.
3900
3901 See `def-projectile-commander-method' for defining new methods."
3902   (interactive)
3903   (let* ((choices (mapcar #'car projectile-commander-methods))
3904          (prompt (concat "Select Projectile command [" choices "]: "))
3905          (ch (read-char-choice prompt choices))
3906          (fn (nth 2 (assq ch projectile-commander-methods))))
3907     (funcall fn)))
3908
3909 (defmacro def-projectile-commander-method (key description &rest body)
3910   "Define a new `projectile-commander' method.
3911
3912 KEY is the key the user will enter to choose this method.
3913
3914 DESCRIPTION is a one-line sentence describing how the method.
3915
3916 BODY is a series of forms which are evaluated when the find
3917 is chosen."
3918   (let ((method `(lambda ()
3919                    ,@body)))
3920     `(setq projectile-commander-methods
3921            (cl-sort (copy-sequence
3922                      (cons (list ,key ,description ,method)
3923                            (assq-delete-all ,key projectile-commander-methods)))
3924                     (lambda (a b) (< (car a) (car b)))))))
3925
3926 (def-projectile-commander-method ?? "Commander help buffer."
3927   (ignore-errors (kill-buffer projectile-commander-help-buffer))
3928   (with-current-buffer (get-buffer-create projectile-commander-help-buffer)
3929     (insert "Projectile Commander Methods:\n\n")
3930     (dolist (met projectile-commander-methods)
3931       (insert (format "%c:\t%s\n" (car met) (cadr met))))
3932     (goto-char (point-min))
3933     (help-mode)
3934     (display-buffer (current-buffer) t))
3935   (projectile-commander))
3936
3937 (defun projectile-commander-bindings ()
3938   "Setup the keybindings for the Projectile Commander."
3939   (def-projectile-commander-method ?f
3940     "Find file in project."
3941     (projectile-find-file))
3942
3943   (def-projectile-commander-method ?T
3944     "Find test file in project."
3945     (projectile-find-test-file))
3946
3947   (def-projectile-commander-method ?b
3948     "Switch to project buffer."
3949     (projectile-switch-to-buffer))
3950
3951   (def-projectile-commander-method ?d
3952     "Find directory in project."
3953     (projectile-find-dir))
3954
3955   (def-projectile-commander-method ?D
3956     "Open project root in dired."
3957     (projectile-dired))
3958
3959   (def-projectile-commander-method ?v
3960     "Open project root in vc-dir or magit."
3961     (projectile-vc))
3962
3963   (def-projectile-commander-method ?V
3964     "Browse dirty projects"
3965     (projectile-browse-dirty-projects))
3966
3967   (def-projectile-commander-method ?r
3968     "Replace a string in the project."
3969     (projectile-replace))
3970
3971   (def-projectile-commander-method ?R
3972     "Regenerate the project's [e|g]tags."
3973     (projectile-regenerate-tags))
3974
3975   (def-projectile-commander-method ?g
3976     "Run grep on project."
3977     (projectile-grep))
3978
3979   (def-projectile-commander-method ?a
3980     "Run ag on project."
3981     (call-interactively 'projectile-ag))
3982
3983   (def-projectile-commander-method ?s
3984     "Switch project."
3985     (projectile-switch-project))
3986
3987   (def-projectile-commander-method ?o
3988     "Run multi-occur on project buffers."
3989     (projectile-multi-occur))
3990
3991   (def-projectile-commander-method ?j
3992     "Find tag in project."
3993     (projectile-find-tag))
3994
3995   (def-projectile-commander-method ?k
3996     "Kill all project buffers."
3997     (projectile-kill-buffers))
3998
3999   (def-projectile-commander-method ?e
4000     "Find recently visited file in project."
4001     (projectile-recentf)))
4002
4003
4004 ;;; Dirty (modified) project check related functionality
4005 (defun projectile-check-vcs-status (&optional project-path)
4006   "Check the status of the current project.
4007 If PROJECT-PATH is a project, check this one instead."
4008   (let ((project-path (or project-path (projectile-project-root)))
4009         (project-status nil))
4010     (save-excursion
4011       (vc-dir project-path)
4012       ;; wait until vc-dir is done
4013       (while (vc-dir-busy) (sleep-for 0 100))
4014       ;; check for status
4015       (save-excursion
4016         (save-match-data
4017           (dolist (check projectile-vcs-dirty-state)
4018             (goto-char (point-min))
4019             (when (search-forward check nil t)
4020               (setq project-status (cons check project-status))))))
4021       (kill-buffer)
4022       project-status)))
4023
4024 (defvar projectile-cached-dirty-projects-status nil
4025   "Cache of the last dirty projects check.")
4026
4027 (defun projectile-check-vcs-status-of-known-projects ()
4028   "Return the list of dirty projects.
4029 The list is composed of sublists~: (project-path, project-status).
4030 Raise an error if their is no dirty project."
4031   (save-window-excursion
4032     (message "Checking for modifications in known projects...")
4033     (let ((projects projectile-known-projects)
4034           (status ()))
4035       (dolist (project projects)
4036         (when (and (projectile-keep-project-p project) (not (string= 'none (projectile-project-vcs project))))
4037           (let ((tmp-status (projectile-check-vcs-status project)))
4038             (when tmp-status
4039               (setq status (cons (list project tmp-status) status))))))
4040       (when (= (length status) 0)
4041         (message "No dirty projects have been found"))
4042       (setq projectile-cached-dirty-projects-status status)
4043       status)))
4044
4045 ;;;###autoload
4046 (defun projectile-browse-dirty-projects (&optional cached)
4047   "Browse dirty version controlled projects.
4048
4049 With a prefix argument, or if CACHED is non-nil, try to use the cached
4050 dirty project list."
4051   (interactive "P")
4052   (let ((status (if (and cached projectile-cached-dirty-projects-status)
4053                     projectile-cached-dirty-projects-status
4054                   (projectile-check-vcs-status-of-known-projects)))
4055         (mod-proj nil))
4056     (while (not (= (length status) 0))
4057       (setq mod-proj (cons (car (pop status)) mod-proj)))
4058     (projectile-completing-read "Select project: " mod-proj
4059                                 :action 'projectile-vc)))
4060
4061
4062 ;;; Find next/previous project buffer
4063 (defun projectile--repeat-until-project-buffer (orig-fun &rest args)
4064   "Repeat ORIG-FUN with ARGS until the current buffer is a project buffer."
4065   (if (projectile-project-root)
4066       (let* ((other-project-buffers (make-hash-table :test 'eq))
4067              (projectile-project-buffers (projectile-project-buffers))
4068              (max-iterations (length (buffer-list)))
4069              (counter 0))
4070         (dolist (buffer projectile-project-buffers)
4071           (unless (eq buffer (current-buffer))
4072             (puthash buffer t other-project-buffers)))
4073         (when (cdr-safe projectile-project-buffers)
4074           (while (and (< counter max-iterations)
4075                       (not (gethash (current-buffer) other-project-buffers)))
4076             (apply orig-fun args)
4077             (cl-incf counter))))
4078     (apply orig-fun args)))
4079
4080 (defun projectile-next-project-buffer ()
4081   "In selected window switch to the next project buffer.
4082
4083 If the current buffer does not belong to a project, call `next-buffer'."
4084   (interactive)
4085   (projectile--repeat-until-project-buffer #'next-buffer))
4086
4087 (defun projectile-previous-project-buffer ()
4088   "In selected window switch to the previous project buffer.
4089
4090 If the current buffer does not belong to a project, call `previous-buffer'."
4091   (interactive)
4092   (projectile--repeat-until-project-buffer #'previous-buffer))
4093
4094
4095 ;;; Editing a project's .dir-locals
4096 (defun projectile-read-variable ()
4097   "Prompt for a variable and return its name."
4098   (completing-read "Variable: "
4099                    obarray
4100                    '(lambda (v)
4101                       (and (boundp v) (not (keywordp v))))
4102                    t))
4103
4104 (define-skeleton projectile-skel-variable-cons
4105   "Insert a variable-name and a value in a cons-cell."
4106   "Value: "
4107   "("
4108   (projectile-read-variable)
4109   " . "
4110   str
4111   ")")
4112
4113 (define-skeleton projectile-skel-dir-locals
4114   "Insert a .dir-locals.el template."
4115   nil
4116   "((nil . ("
4117   ("" '(projectile-skel-variable-cons) \n)
4118   resume:
4119   ")))")
4120
4121 ;;;###autoload
4122 (defun projectile-edit-dir-locals ()
4123   "Edit or create a .dir-locals.el file of the project."
4124   (interactive)
4125   (let ((file (expand-file-name ".dir-locals.el" (projectile-project-root))))
4126     (find-file file)
4127     (when (not (file-exists-p file))
4128       (unwind-protect
4129           (projectile-skel-dir-locals)
4130         (save-buffer)))))
4131
4132
4133 ;;; Projectile Minor mode
4134 (define-obsolete-variable-alias 'projectile-mode-line-lighter 'projectile-mode-line-prefix)
4135 (defcustom projectile-mode-line-prefix
4136   " Projectile"
4137   "Mode line lighter prefix for Projectile.
4138 It's used by `projectile-default-mode-line'
4139 when using dynamic mode line lighter and is the only
4140 thing shown in the mode line otherwise."
4141   :group 'projectile
4142   :type 'string
4143   :package-version '(projectile . "0.12.0"))
4144
4145 (defvar-local projectile--mode-line projectile-mode-line-prefix)
4146
4147 (defun projectile-default-mode-line ()
4148   "Report project name and type in the modeline."
4149   (let ((project-name (projectile-project-name))
4150         (project-type (projectile-project-type)))
4151     (format "%s[%s%s]"
4152             projectile-mode-line-prefix
4153             (or project-name "-")
4154             (if project-type
4155                 (format ":%s" project-type)
4156               ""))))
4157
4158 (defun projectile-update-mode-line ()
4159   "Update the Projectile mode-line."
4160   (let ((mode-line (funcall projectile-mode-line-function)))
4161     (setq projectile--mode-line mode-line))
4162   (force-mode-line-update))
4163
4164 (defvar projectile-command-map
4165   (let ((map (make-sparse-keymap)))
4166     (define-key map (kbd "4 a") #'projectile-find-other-file-other-window)
4167     (define-key map (kbd "4 b") #'projectile-switch-to-buffer-other-window)
4168     (define-key map (kbd "4 C-o") #'projectile-display-buffer)
4169     (define-key map (kbd "4 d") #'projectile-find-dir-other-window)
4170     (define-key map (kbd "4 D") #'projectile-dired-other-window)
4171     (define-key map (kbd "4 f") #'projectile-find-file-other-window)
4172     (define-key map (kbd "4 g") #'projectile-find-file-dwim-other-window)
4173     (define-key map (kbd "4 t") #'projectile-find-implementation-or-test-other-window)
4174     (define-key map (kbd "5 a") #'projectile-find-other-file-other-frame)
4175     (define-key map (kbd "5 b") #'projectile-switch-to-buffer-other-frame)
4176     (define-key map (kbd "5 d") #'projectile-find-dir-other-frame)
4177     (define-key map (kbd "5 D") #'projectile-dired-other-frame)
4178     (define-key map (kbd "5 f") #'projectile-find-file-other-frame)
4179     (define-key map (kbd "5 g") #'projectile-find-file-dwim-other-frame)
4180     (define-key map (kbd "5 t") #'projectile-find-implementation-or-test-other-frame)
4181     (define-key map (kbd "!") #'projectile-run-shell-command-in-root)
4182     (define-key map (kbd "&") #'projectile-run-async-shell-command-in-root)
4183     (define-key map (kbd "a") #'projectile-find-other-file)
4184     (define-key map (kbd "b") #'projectile-switch-to-buffer)
4185     (define-key map (kbd "C") #'projectile-configure-project)
4186     (define-key map (kbd "c") #'projectile-compile-project)
4187     (define-key map (kbd "d") #'projectile-find-dir)
4188     (define-key map (kbd "D") #'projectile-dired)
4189     (define-key map (kbd "e") #'projectile-recentf)
4190     (define-key map (kbd "E") #'projectile-edit-dir-locals)
4191     (define-key map (kbd "f") #'projectile-find-file)
4192     (define-key map (kbd "g") #'projectile-find-file-dwim)
4193     (define-key map (kbd "F") #'projectile-find-file-in-known-projects)
4194     (define-key map (kbd "i") #'projectile-invalidate-cache)
4195     (define-key map (kbd "I") #'projectile-ibuffer)
4196     (define-key map (kbd "j") #'projectile-find-tag)
4197     (define-key map (kbd "k") #'projectile-kill-buffers)
4198     (define-key map (kbd "l") #'projectile-find-file-in-directory)
4199     (define-key map (kbd "m") #'projectile-commander)
4200     (define-key map (kbd "o") #'projectile-multi-occur)
4201     (define-key map (kbd "p") #'projectile-switch-project)
4202     (define-key map (kbd "q") #'projectile-switch-open-project)
4203     (define-key map (kbd "P") #'projectile-test-project)
4204     (define-key map (kbd "r") #'projectile-replace)
4205     (define-key map (kbd "R") #'projectile-regenerate-tags)
4206     (define-key map (kbd "s g") #'projectile-grep)
4207     (define-key map (kbd "s r") #'projectile-ripgrep)
4208     (define-key map (kbd "s s") #'projectile-ag)
4209     (define-key map (kbd "S") #'projectile-save-project-buffers)
4210     (define-key map (kbd "t") #'projectile-toggle-between-implementation-and-test)
4211     (define-key map (kbd "T") #'projectile-find-test-file)
4212     (define-key map (kbd "u") #'projectile-run-project)
4213     (define-key map (kbd "v") #'projectile-vc)
4214     (define-key map (kbd "V") #'projectile-browse-dirty-projects)
4215     (define-key map (kbd "x e") #'projectile-run-eshell)
4216     (define-key map (kbd "x i") #'projectile-run-ielm)
4217     (define-key map (kbd "x t") #'projectile-run-term)
4218     (define-key map (kbd "x s") #'projectile-run-shell)
4219     (define-key map (kbd "z") #'projectile-cache-current-file)
4220     (define-key map (kbd "<left>") #'projectile-previous-project-buffer)
4221     (define-key map (kbd "<right>") #'projectile-next-project-buffer)
4222     (define-key map (kbd "ESC") #'projectile-project-buffers-other-buffer)
4223     map)
4224   "Keymap for Projectile commands after `projectile-keymap-prefix'.")
4225 (fset 'projectile-command-map projectile-command-map)
4226
4227 (defvar projectile-mode-map
4228   (let ((map (make-sparse-keymap)))
4229     (when projectile-keymap-prefix
4230       (define-key map projectile-keymap-prefix 'projectile-command-map))
4231     (easy-menu-define projectile-mode-menu map
4232       "Menu for Projectile"
4233       '("Projectile"
4234         ["Find file" projectile-find-file]
4235         ["Find file in known projects" projectile-find-file-in-known-projects]
4236         ["Find test file" projectile-find-test-file]
4237         ["Find directory" projectile-find-dir]
4238         ["Find file in directory" projectile-find-file-in-directory]
4239         ["Find other file" projectile-find-other-file]
4240         ["Switch to buffer" projectile-switch-to-buffer]
4241         ["Jump between implementation file and test file" projectile-toggle-between-implementation-and-test]
4242         ["Kill project buffers" projectile-kill-buffers]
4243         ["Save project buffers" projectile-save-project-buffers]
4244         ["Recent files" projectile-recentf]
4245         ["Previous buffer" projectile-previous-project-buffer]
4246         ["Next buffer" projectile-next-project-buffer]
4247         "--"
4248         ["Toggle project wide read-only" projectile-toggle-project-read-only]
4249         ["Edit .dir-locals.el" projectile-edit-dir-locals]
4250         "--"
4251         ["Switch to project" projectile-switch-project]
4252         ["Switch to open project" projectile-switch-open-project]
4253         ["Discover projects in directory" projectile-discover-projects-in-directory]
4254         ["Browse dirty projects" projectile-browse-dirty-projects]
4255         ["Open project in dired" projectile-dired]
4256         "--"
4257         ["Search in project (grep)" projectile-grep]
4258         ["Search in project (ag)" projectile-ag]
4259         ["Replace in project" projectile-replace]
4260         ["Multi-occur in project" projectile-multi-occur]
4261         "--"
4262         ["Run shell" projectile-run-shell]
4263         ["Run eshell" projectile-run-eshell]
4264         ["Run ielm" projectile-run-ielm]
4265         ["Run term" projectile-run-term]
4266         "--"
4267         ["Cache current file" projectile-cache-current-file]
4268         ["Invalidate cache" projectile-invalidate-cache]
4269         ["Regenerate [e|g]tags" projectile-regenerate-tags]
4270         "--"
4271         ["Configure project" projectile-configure-project]
4272         ["Compile project" projectile-compile-project]
4273         ["Test project" projectile-test-project]
4274         ["Run project" projectile-run-project]
4275         ["Repeat last external command" projectile-repeat-last-command]
4276         "--"
4277         ["Project info" projectile-project-info]
4278         ["About" projectile-version]))
4279     map)
4280   "Keymap for Projectile mode.")
4281
4282 (defun projectile-find-file-hook-function ()
4283   "Called by `find-file-hook' when `projectile-mode' is on.
4284
4285 The function does pretty much nothing when triggered on remote files
4286 as all the operations it normally performs are extremely slow over
4287 tramp."
4288   (unless (file-remote-p default-directory)
4289     (when projectile-dynamic-mode-line
4290       (projectile-update-mode-line))
4291     (projectile-cache-files-find-file-hook)
4292     (projectile-track-known-projects-find-file-hook)
4293     (projectile-visit-project-tags-table)))
4294
4295 ;;;###autoload
4296 (define-minor-mode projectile-mode
4297   "Minor mode to assist project management and navigation.
4298
4299 When called interactively, toggle `projectile-mode'.  With prefix
4300 ARG, enable `projectile-mode' if ARG is positive, otherwise disable
4301 it.
4302
4303 When called from Lisp, enable `projectile-mode' if ARG is omitted,
4304 nil or positive.  If ARG is `toggle', toggle `projectile-mode'.
4305 Otherwise behave as if called interactively.
4306
4307 \\{projectile-mode-map}"
4308   :lighter projectile--mode-line
4309   :keymap projectile-mode-map
4310   :group 'projectile
4311   :require 'projectile
4312   :global t
4313   (cond
4314    (projectile-mode
4315     ;; setup the commander bindings
4316     (projectile-commander-bindings)
4317     ;; initialize the projects cache if needed
4318     (unless projectile-projects-cache
4319       (setq projectile-projects-cache
4320             (or (projectile-unserialize projectile-cache-file)
4321                 (make-hash-table :test 'equal))))
4322     (unless projectile-projects-cache-time
4323       (setq projectile-projects-cache-time
4324             (make-hash-table :test 'equal)))
4325     ;; load the known projects
4326     (projectile-load-known-projects)
4327     ;; update the list of known projects
4328     (projectile--cleanup-known-projects)
4329     (projectile-discover-projects-in-search-path)
4330     (add-hook 'find-file-hook 'projectile-find-file-hook-function)
4331     (add-hook 'projectile-find-dir-hook #'projectile-track-known-projects-find-file-hook t)
4332     (add-hook 'dired-before-readin-hook #'projectile-track-known-projects-find-file-hook t t)
4333     (ad-activate 'compilation-find-file)
4334     (ad-activate 'delete-file))
4335    (t
4336     (remove-hook 'find-file-hook #'projectile-find-file-hook-function)
4337     (remove-hook 'dired-before-readin-hook #'projectile-track-known-projects-find-file-hook t)
4338     (ad-deactivate 'compilation-find-file)
4339     (ad-deactivate 'delete-file))))
4340
4341 ;;;###autoload
4342 (define-obsolete-function-alias 'projectile-global-mode 'projectile-mode "1.0")
4343
4344 (provide 'projectile)
4345
4346 ;;; projectile.el ends here