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 |