From d3d323f8d1637419f5661c7e5ea6e706dc281717 Mon Sep 17 00:00:00 2001 From: Andreas Gerler Date: Sat, 20 Feb 2016 14:06:02 +0100 Subject: [PATCH] update projectile. --- .../projectile-autoloads.el | 5 +- .../projectile-pkg.el | 2 +- .../projectile.el | 868 +++++++++++------- 3 files changed, 540 insertions(+), 335 deletions(-) rename elpa/{projectile-20150701.121 => projectile-20160210.905}/projectile-autoloads.el (96%) rename elpa/{projectile-20150701.121 => projectile-20160210.905}/projectile-pkg.el (69%) rename elpa/{projectile-20150701.121 => projectile-20160210.905}/projectile.el (79%) diff --git a/elpa/projectile-20150701.121/projectile-autoloads.el b/elpa/projectile-20160210.905/projectile-autoloads.el similarity index 96% rename from elpa/projectile-20150701.121/projectile-autoloads.el rename to elpa/projectile-20160210.905/projectile-autoloads.el index d050551a..8af2b16a 100644 --- a/elpa/projectile-20150701.121/projectile-autoloads.el +++ b/elpa/projectile-20160210.905/projectile-autoloads.el @@ -3,7 +3,7 @@ ;;; Code: (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) -;;;### (autoloads nil "projectile" "projectile.el" (21908 14196 0 +;;;### (autoloads nil "projectile" "projectile.el" (22215 5355 0 ;;;;;; 0)) ;;; Generated autoloads from projectile.el @@ -158,6 +158,7 @@ Open `dired' at the root of the project. Open `vc-dir' at the root of the project. For git projects `magit-status-internal' is used if available. +For hg projects `monky-status' is used if available. \(fn &optional PROJECT-ROOT)" t nil) @@ -196,7 +197,7 @@ See `def-projectile-commander-method' for defining new methods. \(fn)" t nil) -(defvar projectile-mode-line '(:eval (format " Projectile[%s]" (projectile-project-name))) "\ +(defvar projectile-mode-line '(:eval (if (file-remote-p default-directory) " Projectile" (format " Projectile[%s]" (projectile-project-name)))) "\ Mode line lighter for Projectile. The value of this variable is a mode line template as in diff --git a/elpa/projectile-20150701.121/projectile-pkg.el b/elpa/projectile-20160210.905/projectile-pkg.el similarity index 69% rename from elpa/projectile-20150701.121/projectile-pkg.el rename to elpa/projectile-20160210.905/projectile-pkg.el index d7b106d0..bf6a2bbe 100644 --- a/elpa/projectile-20150701.121/projectile-pkg.el +++ b/elpa/projectile-20160210.905/projectile-pkg.el @@ -1 +1 @@ -(define-package "projectile" "20150701.121" "Manage and navigate projects in Emacs easily" '((dash "2.11.0") (pkg-info "0.4")) :url "https://github.com/bbatsov/projectile" :keywords '("project" "convenience")) +(define-package "projectile" "20160210.905" "Manage and navigate projects in Emacs easily" '((dash "2.11.0") (pkg-info "0.4")) :url "https://github.com/bbatsov/projectile" :keywords '("project" "convenience")) diff --git a/elpa/projectile-20150701.121/projectile.el b/elpa/projectile-20160210.905/projectile.el similarity index 79% rename from elpa/projectile-20150701.121/projectile.el rename to elpa/projectile-20160210.905/projectile.el index 11566d09..62d8f138 100644 --- a/elpa/projectile-20150701.121/projectile.el +++ b/elpa/projectile-20160210.905/projectile.el @@ -1,12 +1,12 @@ ;;; projectile.el --- Manage and navigate projects in Emacs easily -*- lexical-binding: t -*- -;; Copyright © 2011-2015 Bozhidar Batsov +;; Copyright © 2011-2016 Bozhidar Batsov ;; Author: Bozhidar Batsov ;; URL: https://github.com/bbatsov/projectile -;; Package-Version: 20150701.121 +;; Package-Version: 20160210.905 ;; Keywords: project, convenience -;; Version: 0.13.0-cvs +;; Version: 0.13.0 ;; Package-Requires: ((dash "2.11.0") (pkg-info "0.4")) ;; This file is NOT part of GNU Emacs. @@ -41,8 +41,11 @@ (require 'dash) (require 'ibuffer) (require 'ibuf-ext) +(require 'compile) +(require 'grep) (eval-when-compile + (defvar ag-ignore-list) (defvar ggtags-completion-table) (defvar tags-completion-table)) @@ -51,6 +54,10 @@ (declare-function pkg-info-version-info "pkg-info") (declare-function tags-completion-table "etags") +(defvar grep-files-aliases) +(defvar grep-find-ignored-directories) +(defvar grep-find-ignored-files) + ;;;; Compatibility (eval-and-compile ;; Added in Emacs 24.3. @@ -74,6 +81,35 @@ attention to case differences." (and (>= start-pos 0) (eq t (compare-strings suffix nil nil string start-pos nil ignore-case)))))) + + ;; Improved (no more stack overflows) in Emacs 24.5 + (eval-after-load 'etags + '(when (< emacs-major-version 25) + (defvar etags--table-line-limit 500) + (defun etags-tags-completion-table () + (let (table + (progress-reporter + (make-progress-reporter + (format "Making tags completion table for %s..." buffer-file-name) + (point-min) (point-max)))) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (if (not (re-search-forward + "[\f\t\n\r()=,; ]?\177\\\(?:\\([^\n\001]+\\)\001\\)?" + (+ (point) etags--table-line-limit) t)) + (forward-line 1) + (push (prog1 (if (match-beginning 1) + (buffer-substring (match-beginning 1) (match-end 1)) + (goto-char (match-beginning 0)) + (skip-chars-backward "^\f\t\n\r()=,; ") + (prog1 + (buffer-substring (point) (match-beginning 0)) + (goto-char (match-end 0)))) + (progress-reporter-update progress-reporter (point))) + table)))) + table)))) + ) (defun projectile-trim-string (string) @@ -195,17 +231,36 @@ Otherwise consider the current directory the project root." "A function used to filter the buffers in `projectile-project-buffers'. The function should accept and return a list of Emacs buffers. -Two example filter functions are shipped by default - `projectile-buffers-with-file' -and `projectile-buffers-with-file-or-process'." +Two example filter functions are shipped by default - +`projectile-buffers-with-file' and +`projectile-buffers-with-file-or-process'." :group 'projectile :type 'symbol) +(defcustom projectile-project-name nil + "If this value is non-nil, it will be used as project name. + +It has precedence over function `projectile-project-name-function'." + :group 'projectile + :type 'string + :package-version '(projectile . "0.14.0")) + +(defcustom projectile-project-name-function 'projectile-default-project-name + "A function that receives the project-root and returns the project name. + +If variable `projectile-project-name' is non-nil, this function will not be used." + :group 'projectile + :type 'symbol + :package-version '(projectile . "0.14.0")) + (defcustom projectile-project-root-files '("rebar.config" ; Rebar project file "project.clj" ; Leiningen project file + "build.boot" ; Boot-clj project file "SConstruct" ; Scons project file "pom.xml" ; Maven project file "build.sbt" ; SBT project file + "gradlew" ; Gradle wrapper script "build.gradle" ; Gradle project file "Gemfile" ; Bundler file "requirements.txt" ; Pip file @@ -218,6 +273,9 @@ and `projectile-buffers-with-file-or-process'." "composer.json" ; Composer project file "Cargo.toml" ; Cargo project file "mix.exs" ; Elixir mix project file + "stack.yaml" ; Haskell's stack tool based project + "TAGS" ; etags/ctags are usually in the root of project + "GTAGS" ; GNU Global tags ) "A list of files considered to mark the root of a project. The topmost match has precedence." @@ -250,7 +308,8 @@ containing a root file." :type '(repeat string)) (defcustom projectile-project-root-files-functions - '(projectile-root-bottom-up + '(projectile-root-local + projectile-root-bottom-up projectile-root-top-down projectile-root-top-down-recurring) "A list of functions for finding project roots." @@ -278,7 +337,8 @@ containing a root file." ".bzr" "_darcs" ".tox" - ".svn") + ".svn" + ".stack-work") "A list of directories globally ignored by projectile." :group 'projectile :type '(repeat string)) @@ -332,6 +392,23 @@ Any function that does not take arguments will do." "If true, use `vc-git-grep' in git projects." :group 'projectile :type 'boolean) + +(defcustom projectile-grep-finished-hook nil + "Hooks run when `projectile-grep' finishes." + :group 'projectile + :type 'hook + :package-version '(projectile . "0.14.0")) + +(defcustom projectile-test-prefix-function 'projectile-test-prefix + "Function to find test files prefix based on PROJECT-TYPE." + :group 'projectile + :type 'function) + +(defcustom projectile-test-suffix-function 'projectile-test-suffix + "Function to find test files suffix based on PROJECT-TYPE." + :group 'projectile + :type 'function) + ;;; Idle Timer (defvar projectile-idle-timer nil @@ -379,10 +456,14 @@ The saved data can be restored with `projectile-unserialize'." (defun projectile-unserialize (filename) "Read data serialized by `projectile-serialize' from FILENAME." - (when (file-exists-p filename) - (with-temp-buffer - (insert-file-contents filename) - (read (buffer-string))))) + (with-demoted-errors + "Error during file deserialization: %S" + (when (file-exists-p filename) + (with-temp-buffer + (insert-file-contents filename) + ;; this will blow up if the contents of the file aren't + ;; lisp data structures + (read (buffer-string)))))) (defvar projectile-projects-cache nil "A hashmap used to cache project file names to speed up related operations.") @@ -390,6 +471,9 @@ The saved data can be restored with `projectile-unserialize'." (defvar projectile-project-root-cache (make-hash-table :test 'equal) "Cached value of function `projectile-project-root`.") +(defvar projectile-project-type-cache (make-hash-table :test 'equal) + "A hashmap used to cache project type to speed up related operations.") + (defvar projectile-known-projects nil "List of locations where we have previously seen projects. The list of projects is ordered by the time they have been accessed.") @@ -521,6 +605,7 @@ to invalidate." (projectile-hash-keys projectile-projects-cache)) (projectile-project-root)))) (setq projectile-project-root-cache (make-hash-table :test 'equal)) + (remhash project-root projectile-project-type-cache) (remhash project-root projectile-projects-cache) (projectile-serialize-cache) (when projectile-verbose @@ -533,7 +618,6 @@ to invalidate." "Cache PROJECTs FILES. The cache is created both in memory and on the hard drive." (when projectile-enable-caching - (message "Empty cache. Projectile is initializing cache...") (puthash project files projectile-projects-cache) (projectile-serialize-cache))) @@ -649,6 +733,14 @@ which we're looking." (setq file nil)))) (and root (expand-file-name (file-name-as-directory root))))) +(defvar-local projectile-project-root nil + "Defines a custom Projectile project root. +This is intended to be used as a file local variable.") + +(defun projectile-root-local (_dir) + "A simple wrapper around `projectile-project-root'." + projectile-project-root) + (defun projectile-root-top-down (dir &optional list) "Identify a project root in DIR by top-down search for files in LIST. If LIST is nil, use `projectile-project-root-files' instead. @@ -686,11 +778,9 @@ The current directory is assumed to be the project's root otherwise." (or (--some (let* ((cache-key (format "%s-%s" it dir)) (cache-value (gethash cache-key projectile-project-root-cache))) (if cache-value - (if (eq cache-value 'no-project-root) - nil - cache-value) + cache-value (let ((value (funcall it (file-truename dir)))) - (puthash cache-key (or value 'no-project-root) projectile-project-root-cache) + (puthash cache-key value projectile-project-root-cache) value))) projectile-project-root-files-functions) (if projectile-require-project-root @@ -709,15 +799,21 @@ A thin wrapper around `file-truename' that handles nil." (projectile-project-root) (error nil))) +(defun projectile-default-project-name (project-root) + "Default function used create project name to be displayed based on the value of PROJECT-ROOT." + (file-name-nondirectory (directory-file-name project-root))) + (defun projectile-project-name () "Return project name." - (let ((project-root - (condition-case nil - (projectile-project-root) - (error nil)))) - (if project-root - (file-name-nondirectory (directory-file-name project-root)) - "-"))) + (if projectile-project-name + projectile-project-name + (let ((project-root + (condition-case nil + (projectile-project-root) + (error nil)))) + (if project-root + (funcall projectile-project-name-function project-root) + "-")))) ;;; Project indexing @@ -773,7 +869,7 @@ Files are returned as relative paths to the project root." :group 'projectile :type 'string) -(defcustom projectile-fossil-command "fossil ls" +(defcustom projectile-fossil-command "fossil ls | tr '\\n' '\\0'" "Command used by projectile to get the files in a fossil project." :group 'projectile :type 'string) @@ -817,15 +913,10 @@ Files are returned as relative paths to the project root." (t "")))) (defun projectile-get-all-sub-projects (project) - "Get all sub-projects for a given projects. -PROJECT is base directory to start search recursively." - (let* ((default-directory project) - ;; search for sub-projects under current project `project' - (submodules (mapcar - (lambda (s) - (file-name-as-directory (expand-file-name s default-directory))) - (projectile-files-via-ext-command (projectile-get-sub-projects-command))))) + "Get all sub-projects for a given project. +PROJECT is base directory to start search recursively." + (let ((submodules (projectile-get-immediate-sub-projects project))) (cond ((null submodules) nil) @@ -835,6 +926,33 @@ PROJECT is base directory to start search recursively." (mapcar (lambda (s) (projectile-get-all-sub-projects s)) submodules))))))) +(defun projectile-get-immediate-sub-projects (path) + "Get immediate sub-projects for a given project without recursing. + +PATH is the vcs root or project root from which to start +searching, and should end with an appropriate path delimiter, such as +'/' or a '\\'. + +If the vcs get-sub-projects query returns results outside of path, +they are excluded from the results of this function." + (let* ((default-directory path) + ;; search for sub-projects under current project `project' + (submodules (mapcar + (lambda (s) + (file-name-as-directory (expand-file-name s default-directory))) + (projectile-files-via-ext-command (projectile-get-sub-projects-command)))) + (project-child-folder-regex + (concat "\\`" + (regexp-quote path)))) + + ;; If project root is inside of an VCS folder, but not actually an + ;; VCS root itself, submodules external to the project will be + ;; included in the VCS get sub-projects result. Let's remove them. + (-filter (lambda (submodule) + (string-match-p project-child-folder-regex + submodule)) + submodules))) + (defun projectile-get-sub-projects-files () "Get files from sub-projects recursively." (-flatten @@ -958,23 +1076,29 @@ opened through use of recentf." "Prepend the current project's name to STRING." (format "[%s] %s" (projectile-project-name) string)) +(defun projectile-read-buffer-to-switch (prompt) + "Read the name of a buffer to switch to, prompting with PROMPT. + +This function excludes the current buffer from the offered +choices." + (projectile-completing-read + prompt + (-remove-item (buffer-name (current-buffer)) + (projectile-project-buffer-names)))) + ;;;###autoload (defun projectile-switch-to-buffer () "Switch to a project buffer." (interactive) (switch-to-buffer - (projectile-completing-read - "Switch to buffer: " - (projectile-project-buffer-names)))) + (projectile-read-buffer-to-switch "Switch to buffer: "))) ;;;###autoload (defun projectile-switch-to-buffer-other-window () "Switch to a project buffer and show it in another window." (interactive) (switch-to-buffer-other-window - (projectile-completing-read - "Switch to buffer: " - (projectile-project-buffer-names)))) + (projectile-read-buffer-to-switch "Switch to buffer: "))) ;;;###autoload (defun projectile-display-buffer () @@ -1097,7 +1221,7 @@ prefix the string will be assumed to be an ignore string." (let (keep ignore (dirconfig (projectile-dirconfig-file))) (when (projectile-file-exists-p dirconfig) (with-temp-buffer - (insert-file-contents-literally dirconfig) + (insert-file-contents dirconfig) (while (not (eobp)) (pcase (char-after) (?+ (push (buffer-substring (1+ (point)) (line-end-position)) keep)) @@ -1151,6 +1275,8 @@ https://github.com/abo-abo/swiper"))) (gethash (projectile-project-root) projectile-projects-cache)))) ;; nothing is cached (unless files + (when projectile-enable-caching + (message "Empty cache. Projectile is initializing cache...")) (setq files (-mapcat #'projectile-dir-files (projectile-get-project-directories))) ;; cache the resulting list of files @@ -1168,8 +1294,8 @@ https://github.com/abo-abo/swiper"))) (defun projectile-current-project-dirs () "Return a list of dirs for the current project." (-remove #'null (-distinct - (-map #'file-name-directory - (projectile-current-project-files))))) + (-map #'file-name-directory + (projectile-current-project-files))))) (defun projectile-hash-keys (hash) "Return a list of all HASH keys." @@ -1177,20 +1303,20 @@ https://github.com/abo-abo/swiper"))) (maphash (lambda (k _v) (setq allkeys (cons k allkeys))) hash) allkeys)) - + ;;; Interactive commands (defcustom projectile-other-file-alist '(;; handle C/C++ extensions ("cpp" . ("h" "hpp" "ipp")) ("ipp" . ("h" "hpp" "cpp")) ("hpp" . ("h" "ipp" "cpp")) - ("cxx" . ("hxx" "ixx")) - ("ixx" . ("cxx" "hxx")) - ("hxx" . ("ixx" "cxx")) + ("cxx" . ("h" "hxx" "ixx")) + ("ixx" . ("h" "hxx" "cxx")) + ("hxx" . ("h" "ixx" "cxx")) ("c" . ("h")) ("m" . ("h")) ("mm" . ("h")) - ("h" . ("c" "cpp" "ipp" "hpp" "m" "mm")) + ("h" . ("c" "cpp" "ipp" "hpp" "cxx" "ixx" "hxx" "m" "mm")) ("cc" . ("hh")) ("hh" . ("cc")) @@ -1227,16 +1353,39 @@ Other file extensions can be customized with the variable `projectile-other-file (find-file-other-window (expand-file-name (projectile-completing-read "Switch to: " other-files) (projectile-project-root)))) (error "No other file found"))) +(defun projectile--file-name-sans-extensions (file-name) + "Return FILE-NAME sans any extensions. +The extensions, in a filename, are what follows the first '.', with the exception of a leading '.'" + (setq file-name (file-name-nondirectory file-name)) + (substring file-name 0 (string-match "\\..*" file-name 1))) + +(defun projectile--file-name-extensions (file-name) + "Return FILE-NAME's extensions. +The extensions, in a filename, are what follows the first '.', with the exception of a leading '.'" + ;;would it make sense to return nil instead of an empty string if no extensions are found? + (setq file-name (file-name-nondirectory file-name)) + (substring file-name (-if-let (extensions-start (string-match "\\..*" file-name 1)) (1+ extensions-start) (length file-name)))) + +(defun projectile-associated-file-name-extensions (file-name) + "Return projectile-other-file-extensions associated to FILE-NAME's extensions. +If no associated other-file-extensions for the complete (nested) extension are found, remove subextensions from FILENAME's extensions until a match is found." + (let ((current-extensions (projectile--file-name-extensions (file-name-nondirectory file-name)))) + (catch 'break + (while (not (string= "" current-extensions)) + (-if-let (associated-extensions (cdr (assoc current-extensions projectile-other-file-alist))) + (throw 'break associated-extensions)) + (setq current-extensions (projectile--file-name-extensions current-extensions)))))) + (defun projectile-get-other-files (current-file project-file-list &optional flex-matching) "Narrow to files with the same names but different extensions. Returns a list of possible files for users to choose. With FLEX-MATCHING, match any file that contains the base name of current file" - (let* ((file-ext-list (cdr (assoc (file-name-extension current-file) projectile-other-file-alist))) + (let* ((file-ext-list (projectile-associated-file-name-extensions current-file)) (fulldirname (if (file-name-directory current-file) (file-name-directory current-file) "./")) (dirname (file-name-nondirectory (directory-file-name fulldirname))) - (filename (file-name-base current-file)) + (filename (projectile--file-name-sans-extensions current-file)) (file-list (mapcar (lambda (ext) (if flex-matching (concat ".*" filename ".*" "\." ext "\\'") @@ -1262,7 +1411,7 @@ With FLEX-MATCHING, match any file that contains the base name of current file" (-sort (lambda (file _) (let ((candidate-dirname (file-name-nondirectory (directory-file-name (file-name-directory file))))) (unless (equal fulldirname (file-name-directory file)) - (equal dirname candidate-dirname)))) + (equal dirname candidate-dirname)))) candidates))) candidates)) @@ -1463,36 +1612,61 @@ With a prefix ARG invalidates the cache first." "Check if FILE is a test file." (or (--any? (string-prefix-p it (file-name-nondirectory file)) (-non-nil (list (funcall projectile-test-prefix-function (projectile-project-type))))) - (--any? (string-suffix-p it (file-name-nondirectory file)) + (--any? (string-suffix-p it (file-name-sans-extension (file-name-nondirectory file))) (-non-nil (list (funcall projectile-test-suffix-function (projectile-project-type))))))) (defun projectile-current-project-test-files () "Return a list of test files for the current project." (projectile-test-files (projectile-current-project-files))) -(defvar projectile-rails-rspec '("Gemfile" "app" "lib" "db" "config" "spec")) -(defvar projectile-rails-test '("Gemfile" "app" "lib" "db" "config" "test")) -(defvar projectile-symfony '("composer.json" "app" "src" "vendor")) -(defvar projectile-ruby-rspec '("Gemfile" "lib" "spec")) -(defvar projectile-ruby-test '("Gemfile" "lib" "test")) -(defvar projectile-django '("manage.py")) -(defvar projectile-python-pip '("requirements.txt")) -(defvar projectile-python-pkg '("setup.py")) -(defvar projectile-python-tox '("tox.ini")) -(defvar projectile-scons '("SConstruct")) -(defvar projectile-maven '("pom.xml")) -(defvar projectile-gradle '("build.gradle")) -(defvar projectile-grails '("application.properties" "grails-app")) -(defvar projectile-lein-test '("project.clj")) -(defvar projectile-lein-midje '("project.clj" ".midje.clj")) -(defvar projectile-rebar '("rebar")) -(defvar projectile-sbt '("build.sbt")) -(defvar projectile-make '("Makefile")) -(defvar projectile-grunt '("Gruntfile.js")) -(defvar projectile-gulp '("gulpfile.js")) -(defvar projectile-haskell-cabal '("*.cabal")) -(defvar projectile-rust-cargo '("Cargo.toml")) -(defvar projectile-r '("DESCRIPTION")) +(defvar projectile-project-types (make-hash-table) + "A hash table holding all project types that are known to Projectile.") + +(defun projectile-register-project-type + (project-type marker-files &optional compile-cmd test-cmd run-cmd) + "Register a project type with projectile. + +A project type is defined by PROJECT-TYPE, a set of MARKER-FILES, +a COMPILE-CMD, a TEST-CMD, and a RUN-CMD." + (puthash project-type (list 'marker-files marker-files + 'compile-command compile-cmd + 'test-command test-cmd + 'run-command run-cmd) + projectile-project-types)) + +(projectile-register-project-type 'emacs-cask '("Cask") "cask install") +(projectile-register-project-type 'rails-rspec '("Gemfile" "app" "lib" "db" "config" "spec") "bundle exec rails server" "bundle exec rspec") +(projectile-register-project-type 'rails-test '("Gemfile" "app" "lib" "db" "config" "test") "bundle exec rails server" "bundle exec rake test") +(projectile-register-project-type 'symfony '("composer.json" "app" "src" "vendor") "app/console server:run" "phpunit -c app ") +(projectile-register-project-type 'ruby-rspec '("Gemfile" "lib" "spec") "bundle exec rake" "bundle exec rspec") +(projectile-register-project-type 'ruby-test '("Gemfile" "lib" "test") "bundle exec rake" "bundle exec rake test") +(projectile-register-project-type 'django '("manage.py") "python manage.py runserver" "python manage.py test") +(projectile-register-project-type 'python-pip '("requirements.txt") "python setup.by build" "python -m unittest discover") +(projectile-register-project-type 'python-pkg '("setup.py") "python setup.py build" "python -m unittest discover") +(projectile-register-project-type 'python-tox '("tox.ini") nil "tox") +(projectile-register-project-type 'scons '("SConstruct") "scons" "scons test") +(projectile-register-project-type 'maven '("pom.xml") "mvn clean install" "mvn test") +(projectile-register-project-type 'gradle '("build.gradle") "gradle build" "gradle test") +(projectile-register-project-type 'gradlew '("gradlew") "./gradlew build" "./gradlew test") +(projectile-register-project-type 'grails '("application.properties" "grails-app") "grails package" "grails test-app") +(projectile-register-project-type 'lein-test '("project.clj") "lein compile" "lein test") +(projectile-register-project-type 'lein-midje '("project.clj" ".midje.clj") "lein compile" "lein midje") +(projectile-register-project-type 'boot-clj '("build.boot") "boot aot" "boot test") +(projectile-register-project-type 'rebar '("rebar") "rebar" "rebar eunit") +(projectile-register-project-type 'sbt '("build.sbt") "sbt compile" "sbt test") +(projectile-register-project-type 'make '("Makefile") "make" "make test") +(projectile-register-project-type 'grunt '("Gruntfile.js") "grunt" "grunt test") +(projectile-register-project-type 'gulp '("gulpfile.js") "gulp" "gulp test") +(projectile-register-project-type 'haskell-stack '("stack.yaml") "stack build" "stack build --test") +(projectile-register-project-type 'haskell-cabal #'projectile-cabal "cabal build" "cabal test") +(projectile-register-project-type 'rust-cargo '("Cargo.toml") "cargo build" "cargo test") +(projectile-register-project-type 'r '("DESCRIPTION") "R CMD INSTALL ." (concat "R CMD check -o " temporary-file-directory " .")) +(projectile-register-project-type 'go #'projectile-go "go build ./..." "go test ./...") + +(defun projectile-cabal () + "Check if a project contains *.cabal files but no stack.yaml file." + (and (projectile-verify-file "*.cabal") + (not (projectile-verify-file "stack.yaml")))) (defun projectile-go () "Check if a project contains Go source files." @@ -1509,35 +1683,25 @@ With a prefix ARG invalidates the cache first." "Buffer local var for overriding the auto-detected project type. Normally you'd set this from .dir-locals.el.") +(defun projectile-detect-project-type () + "Detect the type of the current project." + (let ((project-type (-first (lambda (project-type) + (let ((marker (plist-get (gethash project-type projectile-project-types) 'marker-files))) + (if (listp marker) + (and (projectile-verify-files marker) project-type) + (and (funcall marker) project-type)))) + (projectile-hash-keys projectile-project-types)))) + (when project-type + (puthash (projectile-project-root) project-type projectile-project-type-cache)) + project-type)) + (defun projectile-project-type () "Determine the project's type based on its structure." - (cond - (projectile-project-type projectile-project-type) - ((projectile-verify-files projectile-rails-rspec) 'rails-rspec) - ((projectile-verify-files projectile-rails-test) 'rails-test) - ((projectile-verify-files projectile-ruby-rspec) 'ruby-rspec) - ((projectile-verify-files projectile-ruby-test) 'ruby-test) - ((projectile-verify-files projectile-django) 'django) - ((projectile-verify-files projectile-python-tox) 'python-tox) - ((projectile-verify-files projectile-python-pip)'python) - ((projectile-verify-files projectile-python-pkg) 'python) - ((projectile-verify-files projectile-symfony) 'symfony) - ((projectile-verify-files projectile-lein-midje) 'lein-midje) - ((projectile-verify-files projectile-lein-test) 'lein-test) - ((projectile-verify-files projectile-scons) 'scons) - ((projectile-verify-files projectile-maven) 'maven) - ((projectile-verify-files projectile-gradle) 'gradle) - ((projectile-verify-files projectile-grails) 'grails) - ((projectile-verify-files projectile-rebar) 'rebar) - ((projectile-verify-files projectile-sbt) 'sbt) - ((projectile-verify-files projectile-make) 'make) - ((projectile-verify-files projectile-gulp) 'gulp) - ((projectile-verify-files projectile-grunt) 'grunt) - ((projectile-verify-files projectile-haskell-cabal) 'haskell-cabal) - ((projectile-verify-files projectile-rust-cargo) 'rust-cargo) - ((projectile-verify-files projectile-r) 'r) - ((funcall projectile-go-function) 'go) - (t 'generic))) + (if projectile-project-type + projectile-project-type + (or (gethash (projectile-project-root) projectile-project-type-cache) + (projectile-detect-project-type) + 'generic))) (defun projectile-project-info () "Display info for current project." @@ -1635,41 +1799,27 @@ It assumes the test/ folder is at the same level as src/." (find-file (projectile-find-implementation-or-test (buffer-file-name)))) -(defcustom projectile-test-prefix-function 'projectile-test-prefix - "Function to find test files prefix based on PROJECT-TYPE." - :group 'projectile - :type 'function) - -(defcustom projectile-test-suffix-function 'projectile-test-suffix - "Function to find test files suffix based on PROJECT-TYPE." - :group 'projectile - :type 'function) - -(defun projectile-test-affix (project-type) - "Find test files affix based on PROJECT-TYPE." - (or (funcall projectile-test-prefix-function project-type) - (funcall projectile-test-suffix-function project-type) - (error "Project type not supported!"))) - (defun projectile-test-prefix (project-type) "Find default test files prefix based on PROJECT-TYPE." (cond - ((member project-type '(django python)) "test_") + ((member project-type '(django python-pip python-pkg python-tox)) "test_") + ((member project-type '(emacs-cask)) "test-") ((member project-type '(lein-midje)) "t_"))) (defun projectile-test-suffix (project-type) "Find default test files suffix based on PROJECT-TYPE." (cond + ((member project-type '(emacs-cask)) "-test") ((member project-type '(rails-rspec ruby-rspec)) "_spec") - ((member project-type '(rails-test ruby-test lein-test go)) "_test") + ((member project-type '(rails-test ruby-test lein-test boot-clj go)) "_test") ((member project-type '(scons)) "test") ((member project-type '(maven symfony)) "Test") - ((member project-type '(gradle grails)) "Spec"))) + ((member project-type '(gradle gradlew grails)) "Spec"))) (defun projectile-dirname-matching-count (a b) "Count matching dirnames ascending file paths." - (setq a (reverse (split-string (file-name-directory a) "/" t)) - b (reverse (split-string (file-name-directory b) "/" t))) + (setq a (reverse (split-string (or (file-name-directory a) "") "/" t)) + b (reverse (split-string (or (file-name-directory b) "") "/" t))) (let ((common 0)) (while (and a b (string-equal (pop a) (pop b))) (setq common (1+ common))) @@ -1683,13 +1833,16 @@ It assumes the test/ folder is at the same level as src/." (defun projectile-find-matching-test (file) "Compute the name of the test matching FILE." (let* ((basename (file-name-nondirectory (file-name-sans-extension file))) - (test-affix (projectile-test-affix (projectile-project-type))) + (test-prefix (funcall projectile-test-prefix-function (projectile-project-type))) + (test-suffix (funcall projectile-test-suffix-function (projectile-project-type))) (candidates (-filter (lambda (current-file) (let ((name (file-name-nondirectory (file-name-sans-extension current-file)))) - (or (string-equal name (concat test-affix basename)) - (string-equal name (concat basename test-affix))))) + (or (when test-prefix + (string-equal name (concat test-prefix basename))) + (when test-suffix + (string-equal name (concat basename test-suffix)))))) (projectile-current-project-files)))) (cond ((null candidates) nil) @@ -1702,13 +1855,16 @@ It assumes the test/ folder is at the same level as src/." (defun projectile-find-matching-file (test-file) "Compute the name of a file matching TEST-FILE." (let* ((basename (file-name-nondirectory (file-name-sans-extension test-file))) - (test-affix (projectile-test-affix (projectile-project-type))) + (test-prefix (funcall projectile-test-prefix-function (projectile-project-type))) + (test-suffix (funcall projectile-test-suffix-function (projectile-project-type))) (candidates (-filter (lambda (current-file) (let ((name (file-name-nondirectory (file-name-sans-extension current-file)))) - (or (string-equal (concat test-affix name) basename) - (string-equal (concat name test-affix) basename)))) + (or (when test-prefix + (string-equal (concat test-prefix name) basename)) + (when test-suffix + (string-equal (concat name test-suffix) basename))))) (projectile-current-project-files)))) (cond ((null candidates) nil) @@ -1744,19 +1900,20 @@ This is a subset of `grep-read-files', where either a matching entry from (and ext (concat "*." ext))))) (or default-alias default-extension)))) -(defun projectile-grep (&optional arg) +(defun projectile-grep (&optional regexp arg) "Perform rgrep in the project. With a prefix ARG asks for files (globbing-aware) which to grep in. With prefix ARG of `-' (such as `M--'), default the files (without prompt), -to `projectile-grep-default-files'." - (interactive "P") +to `projectile-grep-default-files'. + +With REGEXP given, don't query the user for a regexp." + (interactive "i\nP") (require 'grep) ;; for `rgrep' (let* ((roots (projectile-get-project-directories)) - (search-regexp (if (and transient-mark-mode mark-active) - (buffer-substring (region-beginning) (region-end)) - (read-string (projectile-prepend-project-name "Grep for: ") - (projectile-symbol-at-point)))) + (search-regexp (or regexp + (read-string (projectile-prepend-project-name "Grep for: ") + (projectile-symbol-or-selection-at-point)))) (files (and arg (or (and (equal current-prefix-arg '-) (projectile-grep-default-files)) (read-string (projectile-prepend-project-name "Grep in: ") @@ -1779,7 +1936,8 @@ to `projectile-grep-default-files'." (projectile-ignored-files)) grep-find-ignored-files))) (grep-compute-defaults) - (rgrep search-regexp (or files "* .*") root-dir)))))) + (rgrep search-regexp (or files "* .*") root-dir)))) + (run-hooks 'projectile-grep-finished-hook))) (defun projectile-ag (search-term &optional arg) "Run an ag search with SEARCH-TERM in the project. @@ -1789,14 +1947,20 @@ regular expression." (interactive (list (read-from-minibuffer (projectile-prepend-project-name (format "Ag %ssearch for: " (if current-prefix-arg "regexp " ""))) - (projectile-symbol-at-point)) + (projectile-symbol-or-selection-at-point)) current-prefix-arg)) - (if (fboundp 'ag-regexp) + (if (require 'ag nil 'noerror) (let ((ag-command (if arg 'ag-regexp 'ag)) + (ag-ignore-list (unless (eq (projectile-project-vcs) 'git) + ;; ag supports git ignore files + (-union ag-ignore-list + (append + (projectile-ignored-files-rel) (projectile-ignored-directories-rel) + grep-find-ignored-files grep-find-ignored-directories)))) ;; reset the prefix arg, otherwise it will affect the ag-command (current-prefix-arg nil)) (funcall ag-command search-term (projectile-project-root))) - (error "Ag is not available"))) + (error "Package 'ag' is not available"))) (defun projectile-tags-exclude-patterns () "Return a string with exclude patterns for ctags." @@ -1841,25 +2005,16 @@ regular expression." (defun projectile-find-tag () "Find tag in project." (interactive) - (let ((find-tag-function (if (boundp 'ggtags-mode) 'ggtags-find-tag 'find-tag)) - (tags (if (boundp 'ggtags-mode) - (projectile--tags (all-completions "" ggtags-completion-table)) - (require 'etags) - ;; we have to manually reset the tags-completion-table every time - (setq tags-completion-table nil) - (tags-completion-table) - (projectile--tags tags-completion-table)))) - (funcall find-tag-function (projectile-completing-read "Find tag: " - tags - (projectile-symbol-at-point))))) - -(defun projectile--tags (completion-table) - "Find tags using COMPLETION-TABLE." - (-reject #'null - (-map (lambda (x) - (unless (integerp x) - (prin1-to-string x t))) - completion-table))) + (projectile-visit-project-tags-table) + ;; Auto-discover the user's preference for tags + (let ((find-tag-fn (cond + ((fboundp 'ggtags-find-tag-dwim) + 'ggtags-find-tag-dwim) + ((fboundp 'etags-select-find-tag) + 'etags-select-find-tag) + (t + 'find-tag)))) + (call-interactively find-tag-fn))) (defmacro projectile-with-default-dir (dir &rest body) "Invoke in DIR the BODY." @@ -1947,7 +2102,7 @@ to run the replacement." (interactive "P") (let* ((old-text (read-string (projectile-prepend-project-name "Replace: ") - (projectile-symbol-at-point))) + (projectile-symbol-or-selection-at-point))) (new-text (read-string (projectile-prepend-project-name (format "Replace %s with: " old-text)))) @@ -1958,6 +2113,12 @@ to run the replacement." (files (projectile-files-with-string old-text directory))) (tags-query-replace old-text new-text nil (cons 'list files)))) +(defun projectile-symbol-or-selection-at-point () + "Get the symbol or selected text at point." + (if (use-region-p) + (buffer-substring-no-properties (region-beginning) (region-end)) + (projectile-symbol-at-point))) + (defun projectile-symbol-at-point () "Get the symbol at point and strip its properties." (substring-no-properties (or (thing-at-point 'symbol) ""))) @@ -1994,17 +2155,24 @@ to run the replacement." (defun projectile-vc (&optional project-root) "Open `vc-dir' at the root of the project. -For git projects `magit-status-internal' is used if available." +For git projects `magit-status-internal' is used if available. +For hg projects `monky-status' is used if available." (interactive) (or project-root (setq project-root (projectile-project-root))) - (if (eq (projectile-project-vcs project-root) 'git) - (cond ((fboundp 'magit-status-internal) - (magit-status-internal project-root)) - ((fboundp 'magit-status) - (with-no-warnings (magit-status project-root))) - (t - (vc-dir project-root))) - (vc-dir project-root))) + (let ((vcs (projectile-project-vcs project-root))) + (pcase vcs + (`git + (cond ((fboundp 'magit-status-internal) + (magit-status-internal project-root)) + ((fboundp 'magit-status) + (with-no-warnings (magit-status project-root))) + (t + (vc-dir project-root)))) + (`hg + (if (fboundp 'monky-status) + (monky-status project-root) + (vc-dir project-root))) + (_ (vc-dir project-root))))) ;;;###autoload (defun projectile-recentf () @@ -2026,150 +2194,96 @@ For git projects `magit-status-internal' is used if available." "Serializes the memory cache to the hard drive." (projectile-serialize projectile-projects-cache projectile-cache-file)) -(defvar projectile-rails-compile-cmd "bundle exec rails server") -(defvar projectile-ruby-compile-cmd "bundle exec rake build") -(defvar projectile-ruby-test-cmd "bundle exec rake test") -(defvar projectile-ruby-rspec-cmd "bundle exec rspec") -(defvar projectile-django-compile-cmd "python manage.py runserver") -(defvar projectile-django-test-cmd "python manage.py test") -(defvar projectile-python-compile-cmd "python setup.py build") -(defvar projectile-python-test-cmd "python -m unittest discover") -(defvar projectile-python-tox-test-cmd "tox") -(defvar projectile-symfony-compile-cmd "app/console server:run") -(defvar projectile-symfony-test-cmd "phpunit -c app ") -(defvar projectile-scons-compile-cmd "scons") -(defvar projectile-scons-test-cmd "scons test") -(defvar projectile-maven-compile-cmd "mvn clean install") -(defvar projectile-maven-test-cmd "mvn test") -(defvar projectile-gradle-compile-cmd "gradle build") -(defvar projectile-gradle-test-cmd "gradle test") -(defvar projectile-grails-compile-cmd "grails package") -(defvar projectile-grails-test-cmd "grails test-app") -(defvar projectile-lein-compile-cmd "lein compile") -(defvar projectile-lein-test-cmd "lein test") -(defvar projectile-lein-midje-test-cmd "lein midje") -(defvar projectile-rebar-compile-cmd "rebar") -(defvar projectile-rebar-test-cmd "rebar eunit") -(defvar projectile-sbt-compile-cmd "sbt compile") -(defvar projectile-sbt-test-cmd "sbt test") -(defvar projectile-make-compile-cmd "make") -(defvar projectile-make-test-cmd "make test") -(defvar projectile-grunt-compile-cmd "grunt") -(defvar projectile-grunt-test-cmd "grunt test") -(defvar projectile-gulp-compile-cmd "gulp") -(defvar projectile-gulp-test-cmd "gulp test") -(defvar projectile-go-compile-cmd "go build ./...") -(defvar projectile-go-test-cmd "go test ./...") -(defvar projectile-haskell-cabal-compile-cmd "cabal build") -(defvar projectile-haskell-cabal-test-cmd "cabal test") -(defvar projectile-rust-cargo-compile-cmd "cargo build") -(defvar projectile-rust-cargo-test-cmd "cargo test") -(defvar projectile-r-compile-cmd "R CMD INSTALL .") -(defvar projectile-r-test-cmd (concat "R CMD check -o " - temporary-file-directory " .")) - -(--each '(projectile-rails-compile-cmd - projectile-ruby-compile-cmd - projectile-ruby-test-cmd - projectile-ruby-rspec-cmd - projectile-django-compile-cmd - projectile-django-test-cmd - projectile-python-compile-cmd - projectile-python-test-cmd - projectile-python-tox-test-cmd - projectile-symfony-compile-cmd - projectile-symfony-test-cmd - projectile-scons-compile-cmd - projectile-scons-test-cmd - projectile-maven-compile-cmd - projectile-maven-test-cmd - projectile-lein-compile-cmd - projectile-lein-test-cmd - projectile-lein-midje-test-cmd - projectile-rebar-compile-cmd - projectile-rebar-test-cmd - projectile-sbt-compile-cmd - projectile-sbt-test-cmd - projectile-make-compile-cmd - projectile-make-test-cmd - projectile-grunt-compile-cmd - projectile-grunt-test-cmd - projectile-haskell-cabal-compile-cmd - projectile-haskell-cabal-test-cmd - projectile-rust-cargo-compile-cmd - projectile-rust-cargo-test-cmd - projectile-r-compile-cmd - projectile-r-test-cmd) - (put it 'safe-local-variable #'stringp)) - - (defvar projectile-compilation-cmd-map (make-hash-table :test 'equal) "A mapping between projects and the last compilation command used on them.") + (defvar projectile-test-cmd-map (make-hash-table :test 'equal) "A mapping between projects and the last test command used on them.") +(defvar projectile-run-cmd-map + (make-hash-table :test 'equal) + "A mapping between projects and the last run command used on them.") + +(defvar projectile-project-compilation-cmd nil + "The command to use with `projectile-compile-project'. +It takes precedence over the default command for the project type when set. +Should be set via .dir-locals.el.") + +(defvar projectile-project-compilation-dir nil + "The directory to use with `projectile-compile-project'. +The directory path is relative to the project root. +Should be set via .dir-locals.el.") + +(defvar projectile-project-test-cmd nil + "The command to use with `projectile-test-project'. +It takes precedence over the default command for the project type when set. +Should be set via .dir-locals.el.") + +(defvar projectile-project-run-cmd nil + "The command to use with `projectile-run-project'. +It takes precedence over the default command for the project type when set. +Should be set via .dir-locals.el.") + (defun projectile-default-compilation-command (project-type) "Retrieve default compilation command for PROJECT-TYPE." - (cond - ((member project-type '(rails-rspec rails-test)) projectile-rails-compile-cmd) - ((member project-type '(ruby-rspec ruby-test)) projectile-ruby-compile-cmd) - ((eq project-type 'django) projectile-django-compile-cmd) - ((eq project-type 'python) projectile-python-compile-cmd) - ((eq project-type 'symfony) projectile-symfony-compile-cmd) - ((member project-type '(lein-test lein-midje)) projectile-lein-compile-cmd) - ((eq project-type 'make) projectile-make-compile-cmd) - ((eq project-type 'rebar) projectile-rebar-compile-cmd) - ((eq project-type 'scons) projectile-scons-compile-cmd) - ((eq project-type 'maven) projectile-maven-compile-cmd) - ((eq project-type 'gradle) projectile-gradle-compile-cmd) - ((eq project-type 'grails) projectile-grails-compile-cmd) - ((eq project-type 'sbt) projectile-sbt-compile-cmd) - ((eq project-type 'grunt) projectile-grunt-compile-cmd) - ((eq project-type 'gulp) projectile-gulp-compile-cmd) - ((eq project-type 'go) projectile-go-compile-cmd) - ((eq project-type 'haskell-cabal) projectile-haskell-cabal-compile-cmd) - ((eq project-type 'rust-cargo) projectile-rust-cargo-compile-cmd) - ((eq project-type 'r) projectile-r-compile-cmd) - (t projectile-make-compile-cmd))) + (plist-get (gethash project-type projectile-project-types) 'compile-command)) (defun projectile-default-test-command (project-type) "Retrieve default test command for PROJECT-TYPE." - (cond - ((member project-type '(rails-rspec ruby-rspec)) projectile-ruby-rspec-cmd) - ((member project-type '(rails-test ruby-test)) projectile-ruby-test-cmd) - ((eq project-type 'django) projectile-django-test-cmd) - ((eq project-type 'python-tox) projectile-python-tox-test-cmd) - ((eq project-type 'python) projectile-python-test-cmd) - ((eq project-type 'symfony) projectile-symfony-test-cmd) - ((eq project-type 'lein-test) projectile-lein-test-cmd) - ((eq project-type 'lein-midje) projectile-lein-midje-test-cmd) - ((eq project-type 'make) projectile-make-test-cmd) - ((eq project-type 'rebar) projectile-rebar-test-cmd) - ((eq project-type 'scons) projectile-scons-test-cmd) - ((eq project-type 'maven) projectile-maven-test-cmd) - ((eq project-type 'gradle) projectile-gradle-test-cmd) - ((eq project-type 'grails) projectile-grails-test-cmd) - ((eq project-type 'sbt) projectile-sbt-test-cmd) - ((eq project-type 'grunt) projectile-grunt-test-cmd) - ((eq project-type 'gulp) projectile-gulp-test-cmd) - ((eq project-type 'go) projectile-go-test-cmd) - ((eq project-type 'haskell-cabal) projectile-haskell-cabal-test-cmd) - ((eq project-type 'rust-cargo) projectile-rust-cargo-test-cmd) - ((eq project-type 'r) projectile-r-test-cmd) - (t projectile-make-test-cmd))) - -(defun projectile-compilation-command (project) - "Retrieve the compilation command for PROJECT." - (or (gethash project projectile-compilation-cmd-map) + (plist-get (gethash project-type projectile-project-types) 'test-command)) + +(defun projectile-default-run-command (project-type) + "Retrieve default run command for PROJECT-TYPE." + (plist-get (gethash project-type projectile-project-types) 'run-command)) + +(defun projectile-compilation-command (compile-dir) + "Retrieve the compilation command for COMPILE-DIR." + (or (gethash compile-dir projectile-compilation-cmd-map) + projectile-project-compilation-cmd (projectile-default-compilation-command (projectile-project-type)))) (defun projectile-test-command (project) "Retrieve the test command for PROJECT." (or (gethash project projectile-test-cmd-map) + projectile-project-test-cmd (projectile-default-test-command (projectile-project-type)))) +(defun projectile-run-command (project) + "Retrieve the run command for PROJECT." + (or (gethash project projectile-run-cmd-map) + projectile-project-run-cmd + (projectile-default-run-command (projectile-project-type)))) + +(defun projectile-read-command (prompt command) + "Adapted from `compilation-read-command'." + (read-shell-command prompt command + (if (equal (car compile-history) command) + '(compile-history . 1) + 'compile-history))) + +(defun projectile-compilation-dir () + "Choose the directory to use for project compilation." + (if projectile-project-compilation-dir + (file-truename + (concat (file-name-as-directory (projectile-project-root)) + (file-name-as-directory projectile-project-compilation-dir))) + (projectile-project-root))) + +(defun projectile-maybe-read-command (arg default-cmd prompt) + "Prompt user for command unless DEFAULT-CMD is an Elisp function." + (if (and (or (stringp default-cmd) (null default-cmd)) + (or compilation-read-command arg)) + (projectile-read-command prompt default-cmd) + default-cmd)) + +(defun projectile-run-compilation (cmd) + "Run external or Elisp compilation command CMD." + (if (functionp cmd) + (funcall cmd) + (compilation-start cmd))) + (defun projectile-compile-project (arg &optional dir) "Run project compilation command. @@ -2177,40 +2291,37 @@ Normally you'll be prompted for a compilation command, unless variable `compilation-read-command'. You can force the prompt with a prefix ARG." (interactive "P") - (let* ((project-root (if dir - dir - (projectile-project-root))) - (default-directory project-root) - (default-cmd (projectile-compilation-command project-root)) - (compilation-cmd (if (or compilation-read-command arg) - (compilation-read-command default-cmd) - default-cmd))) - (puthash project-root compilation-cmd projectile-compilation-cmd-map) + (let* ((project-root (projectile-project-root)) + (default-directory (or dir (projectile-compilation-dir))) + (default-cmd (projectile-compilation-command default-directory)) + (compilation-cmd (projectile-maybe-read-command arg default-cmd "Compile command: "))) + (puthash default-directory compilation-cmd projectile-compilation-cmd-map) (save-some-buffers (not compilation-ask-about-save) (lambda () (projectile-project-buffer-p (current-buffer) project-root))) - (compilation-start compilation-cmd))) + (projectile-run-compilation compilation-cmd))) (defadvice compilation-find-file (around projectile-compilation-find-file) "Try to find a buffer for FILENAME, if we cannot find it, fallback to the original function." (let ((filename (ad-get-arg 1))) - (setf ad-return-value - (or - (if (file-exists-p (expand-file-name filename)) - (find-file-noselect filename)) - ;; Try to find the filename using projectile - (and (projectile-project-p) - (let ((root (projectile-project-root)) - (dirs (cons "" (projectile-current-project-dirs)))) - (-when-let (full-filename (->> dirs - (--map (expand-file-name filename (expand-file-name it root))) - (-filter #'file-exists-p) - (-first-item))) - (find-file-noselect full-filename)))) - ;; Fall back to the old function `compilation-find-file' - ad-do-it)))) + (ad-set-arg 1 + (or + (if (file-exists-p (expand-file-name filename)) + filename) + ;; Try to find the filename using projectile + (and (projectile-project-p) + (let ((root (projectile-project-root)) + (dirs (cons "" (projectile-current-project-dirs)))) + (-when-let (full-filename (->> dirs + (--map (expand-file-name filename (expand-file-name it root))) + (-filter #'file-exists-p) + (-first-item))) + full-filename))) + ;; Fall back to the old argument + filename)) + ad-do-it)) ;; TODO - factor this duplication out (defun projectile-test-project (arg) @@ -2222,19 +2333,50 @@ with a prefix ARG." (interactive "P") (let* ((project-root (projectile-project-root)) (default-cmd (projectile-test-command project-root)) - (test-cmd (if (or compilation-read-command arg) - (compilation-read-command default-cmd) - default-cmd)) + (test-cmd (projectile-maybe-read-command arg default-cmd "Test command: ")) (default-directory project-root)) (puthash project-root test-cmd projectile-test-cmd-map) - (compilation-start test-cmd))) + (projectile-run-compilation test-cmd))) -(defun projectile-relevant-known-projects () - "Return a list of known projects except the current one (if present)." +(defun projectile-run-project (arg) + "Run project run command. + +Normally you'll be prompted for a compilation command, unless +variable `compilation-read-command'. You can force the prompt +with a prefix ARG." + (interactive "P") + (let* ((project-root (projectile-project-root)) + (default-cmd (projectile-run-command project-root)) + (run-cmd (projectile-maybe-read-command arg default-cmd "Run command: ")) + (default-directory project-root)) + (puthash project-root run-cmd projectile-run-cmd-map) + (projectile-run-compilation run-cmd))) + +(defun projectile-open-projects () + "Return a list of all open projects. +An open project is a project with any open buffers." + (-distinct + (-non-nil + (-map (lambda (buffer) + (with-current-buffer buffer + (when (projectile-project-p) + (abbreviate-file-name (projectile-project-root))))) + (buffer-list))))) + +(defun projectile--remove-current-project (projects) + "Remove the current project (if any) from the list of PROJECTS." (if (projectile-project-p) - (-difference projectile-known-projects + (-difference projects (list (abbreviate-file-name (projectile-project-root)))) - projectile-known-projects)) + projects)) + +(defun projectile-relevant-known-projects () + "Return a list of known projects except the current one (if present)." + (projectile--remove-current-project projectile-known-projects)) + +(defun projectile-relevant-open-projects () + "Return a list of open projects except the current one (if present)." + (projectile--remove-current-project (projectile-open-projects))) (defun projectile-switch-project (&optional arg) "Switch to a project we have visited before. @@ -2248,6 +2390,18 @@ With a prefix ARG invokes `projectile-commander' instead of arg) (error "There are no known projects"))) +(defun projectile-switch-open-project (&optional arg) + "Switch to a project we have currently opened. +Invokes the command referenced by `projectile-switch-project-action' on switch. +With a prefix ARG invokes `projectile-commander' instead of +`projectile-switch-project-action.'" + (interactive "P") + (-if-let (projects (projectile-relevant-open-projects)) + (projectile-switch-project-by-name + (projectile-completing-read "Switch to open project: " projects) + arg) + (error "There are no open projects"))) + (defun projectile-switch-project-by-name (project-to-switch &optional arg) "Switch to project by project name PROJECT-TO-SWITCH. Invokes the command referenced by `projectile-switch-project-action' on switch. @@ -2257,8 +2411,9 @@ With a prefix ARG invokes `projectile-commander' instead of (switch-project-action (if arg 'projectile-commander projectile-switch-project-action))) + (run-hooks 'projectile-before-switch-project-hook) (funcall switch-project-action) - (run-hooks 'projectile-switch-project-hook))) + (run-hooks 'projectile-after-switch-project-hook))) (defun projectile-find-file-in-directory (&optional directory) @@ -2294,15 +2449,21 @@ This command will first prompt for the directory the file is in." (let ((projectile-require-project-root nil)) (find-file (projectile-completing-read "Find file in projects: " (projectile-all-project-files))))) -(defcustom projectile-switch-project-hook nil - "Hooks run when project is switched." +(defcustom projectile-after-switch-project-hook nil + "Hooks run right after project is switched." + :group 'projectile + :type 'hook) + +(defcustom projectile-before-switch-project-hook nil + "Hooks run when right before project is switched." :group 'projectile :type 'hook) (defun projectile-keep-project-p (project) - "Determine wether we should cleanup this project or not. + "Determine whether we should cleanup (remove) PROJECT or not. -It handles the case of remote files as well. See `projectile-cleanup-known-projects'." +It handles the case of remote projects as well. +See `projectile-cleanup-known-projects'." ;; Taken from from `recentf-keep-default-predicate' (cond ((file-remote-p project nil t) (file-readable-p project)) @@ -2415,7 +2576,7 @@ overwriting each other's changes." (defun projectile-ibuffer-by-project (project-root) "Open an IBuffer window showing all buffers in PROJECT-ROOT." - (let ((project-name (file-name-nondirectory (directory-file-name project-root)))) + (let ((project-name (funcall projectile-project-name-function project-root))) (ibuffer nil (format "*%s Buffers*" project-name) (list (cons 'projectile-files project-root))))) @@ -2547,6 +2708,41 @@ is chosen." (projectile-commander-bindings) +(defun projectile-read-variable () + "Prompt for a variable and return its name." + (completing-read "Variable: " + obarray + '(lambda (v) + (and (boundp v) (not (keywordp v)))) + t)) + +(define-skeleton projectile-skel-variable-cons + "Insert a variable-name and a value in a cons-cell." + "Value: " + "(" + (projectile-read-variable) + " . " + str + ")") + +(define-skeleton projectile-skel-dir-locals + "Insert a .dir-locals.el template." + nil + "((nil . ((" + ("" '(projectile-skel-variable-cons) \n) + resume: + "))))") + +(defun projectile-edit-dir-locals () + "Edit or create a .dir-locals.el file of the project." + (interactive) + (let ((file (expand-file-name ".dir-locals.el" (projectile-project-root)))) + (find-file file) + (when (not (file-exists-p file)) + (unwind-protect + (projectile-skel-dir-locals) + (save-buffer))))) + ;;; Minor mode (defvar projectile-command-map (let ((map (make-sparse-keymap))) @@ -2565,6 +2761,7 @@ is chosen." (define-key map (kbd "d") #'projectile-find-dir) (define-key map (kbd "D") #'projectile-dired) (define-key map (kbd "e") #'projectile-recentf) + (define-key map (kbd "E") #'projectile-edit-dir-locals) (define-key map (kbd "f") #'projectile-find-file) (define-key map (kbd "g") #'projectile-find-file-dwim) (define-key map (kbd "F") #'projectile-find-file-in-known-projects) @@ -2576,6 +2773,7 @@ is chosen." (define-key map (kbd "m") #'projectile-commander) (define-key map (kbd "o") #'projectile-multi-occur) (define-key map (kbd "p") #'projectile-switch-project) + (define-key map (kbd "q") #'projectile-switch-open-project) (define-key map (kbd "P") #'projectile-test-project) (define-key map (kbd "r") #'projectile-replace) (define-key map (kbd "R") #'projectile-regenerate-tags) @@ -2584,6 +2782,7 @@ is chosen." (define-key map (kbd "S") #'projectile-save-project-buffers) (define-key map (kbd "t") #'projectile-toggle-between-implementation-and-test) (define-key map (kbd "T") #'projectile-find-test-file) + (define-key map (kbd "u") #'projectile-run-project) (define-key map (kbd "v") #'projectile-vc) (define-key map (kbd "z") #'projectile-cache-current-file) (define-key map (kbd "ESC") #'projectile-project-buffers-other-buffer) @@ -2609,9 +2808,11 @@ is chosen." ["Jump between implementation file and test file" projectile-toggle-between-implementation-and-test] ["Kill project buffers" projectile-kill-buffers] ["Recent files" projectile-recentf] + ["Edit .dir-locals.el" projectile-edit-dir-locals] "--" ["Open project in dired" projectile-dired] ["Switch to project" projectile-switch-project] + ["Switch to open project" projectile-switch-open-project] ["Search in project (grep)" projectile-grep] ["Search in project (ag)" projectile-ag] ["Replace in project" projectile-replace] @@ -2623,6 +2824,7 @@ is chosen." "--" ["Compile project" projectile-compile-project] ["Test project" projectile-test-project] + ["Run project" projectile-run-project] "--" ["Project info" projectile-project-info] ["About" projectile-version]) @@ -2632,7 +2834,9 @@ is chosen." ;;;###autoload (defcustom projectile-mode-line - '(:eval (format " Projectile[%s]" (projectile-project-name))) + '(:eval (if (file-remote-p default-directory) + " Projectile" + (format " Projectile[%s]" (projectile-project-name)))) "Mode line lighter for Projectile. The value of this variable is a mode line template as in -- 2.47.3