├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── CHANGELOG.md ├── README.md ├── helm-projectile.el └── screenshots └── helm-projectile.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: bbatsov 4 | patreon: bbatsov 5 | custom: https://www.paypal.me/bbatsov 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected behavior 2 | 3 | 4 | 5 | ## Actual behavior 6 | 7 | 8 | 9 | ## Steps to reproduce the problem 10 | 11 | 12 | 13 | ## Backtraces if necessary (`M-x toggle-debug-on-error`) 14 | 15 | 16 | 17 | ## Environment & version information 18 | 19 | - `helm-projectile` version: 20 | - `helm` version (in `helm-pkg.el`): 21 | - `projectile` version (`M-x projectile-version`): 22 | - Emacs version (`M-x emacs-version`): 23 | - OS: 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Replace this placeholder text with a summary of the changes in your PR. 2 | The more detailed you are, the better.** 3 | 4 | ----------------- 5 | 6 | Before submitting a PR make sure the following things have been done (and denote this 7 | by checking the relevant checkboxes): 8 | 9 | - [ ] The commits are consistent with our [contribution guidelines](../blob/master/CONTRIBUTING.md) 10 | - [ ] The new code is not generating bytecode or `M-x checkdoc` warnings 11 | - [ ] You've updated the [changelog](../blob/master/CHANGELOG.md) (if adding/changing user-visible functionality) 12 | - [ ] You've updated the readme (if adding/changing user-visible functionality) 13 | 14 | Thanks! 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## master (unreleased) 4 | 5 | ### Bugs fixed 6 | 7 | * [#188](https://github.com/bbatsov/helm-projectile/pull/178): Fix helm-projectile-projects-source slots 8 | 9 | ## 1.1.0 (2025-02-14) 10 | 11 | ### New features 12 | 13 | * Improve `helm-source-proctile-project-list` by also inheriting the 14 | `helm-type-file` class to benefit of it's functionality such as candidate 15 | transformer or file cache. 16 | * [#180](https://github.com/bbatsov/helm-projectile/pull/180): Introduce `helm-projectile-ignore-strategy` defcustom. 17 | 18 | ### Changes 19 | 20 | * [#151](https://github.com/bbatsov/helm-projectile/pull/157): Teach `helm-projectile-rg` to respect ignored files and directories. 21 | * [#151](https://github.com/bbatsov/helm-projectile/issues/151): Rename `helm-projectile-switch-to-eshell` -> `helm-projectile-switch-to-shell`. 22 | 23 | ### Bugs fixed 24 | 25 | * [#176](https://github.com/bbatsov/helm-projectile/pull/178): Correctly remove current buffer from `helm-source-projectile-buffers-list`. 26 | * [#143](https://github.com/bbatsov/helm-projectile/issues/143): Fix rg command for helm-ag arity. 27 | * [#145](https://github.com/bbatsov/helm-projectile/issues/145): Fix bug in `M-D` / remove from project list action for first project in the list. 28 | * [#143](https://github.com/bbatsov/helm-projectile/issues/143): Fix `rg` command for `helm-ag` arity. 29 | * [#140](https://github.com/bbatsov/helm-projectile/pull/140): Fix interactive options for `helm-projectile-grep` and `helm-projectile-ack`. 30 | 31 | ## 1.0.0 (2020-05-18) 32 | 33 | Initial stable release. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt) 2 | [![MELPA](http://melpa.org/packages/helm-projectile-badge.svg)](http://melpa.org/#/helm-projectile) 3 | [![MELPA Stable](http://stable.melpa.org/packages/helm-projectile-badge.svg)](http://stable.melpa.org/#/helm-projectile) 4 | [![Patreon](https://img.shields.io/badge/patreon-donate-orange.svg)](https://www.patreon.com/bbatsov) 5 | 6 | ## Helm Projectile 7 | 8 | [Projectile](https://github.com/bbatsov/projectile) can be integrated 9 | with [Helm](https://github.com/emacs-helm/helm) via 10 | `helm-source-projectile-projects`, 11 | `helm-source-projectile-files-list`, 12 | `helm-source-projectile-buffers-list` and 13 | `helm-source-projectile-recentf-list` sources (available in 14 | `helm-projectile.el`). There is also an example function for calling 15 | Helm with the Projectile file source. You can call it like this: 16 | 17 | ``` 18 | M-x helm-projectile 19 | ``` 20 | 21 | or even better - invoke the key binding h in Projectile's 22 | keymap. Assuming you've opted for one of the recommended keymap prefixes (`C-c p` 23 | or `s-p`), that would mean C-c p h or s-p h. 24 | 25 | See Projectile's [installation docs](https://docs.projectile.mx/projectile/installation.html#installation-via-package-el) for more details. 26 | 27 | ## Project Status 28 | 29 | The project is currently looking for more maintainers. I (Bozhidar) don't use Helm myself and 30 | don't have much time for the project, so I would definitely appreciate some help with it. 31 | 32 | Contact me via e-mail or GitHub if you'd like to become a co-maintainer. 33 | 34 | ## Installation 35 | 36 | The recommended way to install helm-projectile is via `package.el`. 37 | 38 | ### package.el 39 | 40 | #### MELPA 41 | 42 | You can install a snapshot version of helm-projectile from the 43 | [MELPA](http://melpa.org) repository. The version of 44 | Projectile there will always be up-to-date, but it might be unstable 45 | (albeit rarely). 46 | 47 | #### MELPA Stable 48 | 49 | You can install the last stable version of helm-projectile from the 50 | [MELPA Stable](http://stable.melpa.org) repository. 51 | 52 | ### el-get 53 | 54 | helm-projectile is also available for installation from the 55 | [el-get](https://github.com/dimitri/el-get) package manager. 56 | 57 | ### Emacs Prelude 58 | 59 | helm-projectile is naturally part of the 60 | [Emacs Prelude](https://github.com/bbatsov/prelude). If you're a Prelude 61 | user - helm-projectile is already properly configured and ready for 62 | action. 63 | 64 | ### Debian and Ubuntu 65 | 66 | Users of Debian 9 or later or Ubuntu 16.04 or later may `apt 67 | install elpa-helm-projectile`. 68 | 69 | ## Usage 70 | 71 | For those who prefer helm to ido/ivy, the command `helm-projectile-switch-project` 72 | can be used to replace `projectile-switch-project` to switch project. Please 73 | note that this is different from simply setting `projectile-completion-system` 74 | to `helm`, which just enables projectile to use the Helm completion to complete 75 | a project name. The benefit of using `helm-projectile-switch-project` is that on 76 | any selected project we can fire many actions, not limited to just the "switch 77 | to project" action, as in the case of using helm completion by setting 78 | `projectile-completion-system` to `helm`. Currently, there are five actions: 79 | "Switch to project", "Open Dired in project's directory", "Open project root in 80 | vc-dir or magit", "Switch to Eshell" and "Grep project files". We will add more 81 | and more actions in the future. 82 | 83 | `helm-projectile` is capable of opening multiple files by marking the files with 84 | C-SPC or mark all files with M-a. Then, press RET, 85 | all the selected files will be opened. 86 | 87 | Note that the helm grep is different from `projectile-grep` because the helm 88 | grep is incremental. To use it, select your projects (select multiple projects 89 | by pressing C-SPC), press "C-s" (or "C-u C-s" for recursive grep), and type your 90 | regexp. As you type the regexp in the mini buffer, the live grep results are 91 | displayed incrementally. 92 | 93 | `helm-projectile` also provides Helm versions of common Projectile commands. Currently, 94 | these are the supported commands: 95 | 96 | * `helm-projectile-switch-project` 97 | * `helm-projectile-find-file` 98 | * `helm-projectile-find-file-in-known-projects` 99 | * `helm-projectile-find-file-dwim` 100 | * `helm-projectile-find-dir` 101 | * `helm-projectile-recentf` 102 | * `helm-projectile-switch-to-buffer` 103 | * `helm-projectile-grep` (can be used for both grep or ack) 104 | * `helm-projectile-ag` 105 | * `helm-projectile-rg` 106 | * Replace Helm equivalent commands in `projectile-commander` 107 | * A virtual directory manager that is unique to Helm Projectile 108 | 109 | Why should you use these commands compared with the normal Projectile 110 | commands, even if the normal commands use `helm` as 111 | `projectile-completion-system`? The answer is, Helm specific commands 112 | give more useful features. For example, 113 | `helm-projectile-switch-project` allows opening a project in Dired, 114 | Magit or Eshell. `helm-projectile-find-file` reuses actions in 115 | `helm-find-files` (which is plenty) and able to open multiple 116 | files. Another reason is that in a large source tree, helm-projectile 117 | could be slow because it has to open all available sources. 118 | 119 | If you want to use these commands, you have to activate it to replace 120 | the normal Projectile commands: 121 | 122 | ```el 123 | ;; (setq helm-projectile-fuzzy-match nil) 124 | (require 'helm-projectile) 125 | (helm-projectile-on) 126 | ``` 127 | 128 | If you already activate helm-projectile key bindings and you don't 129 | like it, you can turn it off and use the normal Projectile bindings 130 | with command `helm-projectile-off`. Similarly, if you want to disable 131 | fuzzy matching in Helm Projectile (it is enabled by default), you must 132 | set `helm-projectile-fuzzy-match` to nil before loading 133 | `helm-projectile`. 134 | 135 | To fully learn Helm Projectile and see what it is capable of, you 136 | should refer to this guide: 137 | [Exploring large projects with Projectile and Helm Projectile](http://tuhdo.github.io/helm-projectile.html). 138 | 139 | Obviously you need to have Helm installed for this to work. :-) 140 | 141 | ![Helm-Projectile Screenshot](screenshots/helm-projectile.png) 142 | 143 | ## Known issues 144 | 145 | Check out the project's 146 | [issue list](https://github.com/bbatsov/helm-projectile/issues?sort=created&direction=desc&state=open) 147 | a list of unresolved issues. By the way - feel free to fix any of them 148 | and sent me a pull request. :-) 149 | 150 | ## Contributors 151 | 152 | Here's a [list](https://github.com/bbatsov/helm-projectile/contributors) of all the people who have contributed to the 153 | development of Projectile. 154 | 155 | ## Changelog 156 | 157 | A fairly extensive changelog is available [here](CHANGELOG.md). 158 | 159 | **Note:** We started keeping track of changes there after version 1.0. 160 | 161 | ## License 162 | 163 | Copyright © 2011-2025 Bozhidar Batsov and 164 | [contributors](https://github.com/bbatsov/helm-projectile/contributors). 165 | 166 | Distributed under the GNU General Public License, version 3 167 | 168 | [badge-license]: https://img.shields.io/badge/license-GPLv3-blue.svg 169 | -------------------------------------------------------------------------------- /helm-projectile.el: -------------------------------------------------------------------------------- 1 | ;;; helm-projectile.el --- Helm integration for Projectile -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2011-2025 Bozhidar Batsov 4 | 5 | ;; Author: Bozhidar Batsov 6 | ;; URL: https://github.com/bbatsov/helm-projectile 7 | ;; Created: 2011-31-07 8 | ;; Keywords: project, convenience 9 | ;; Version: 1.1.0 10 | ;; Package-Requires: ((helm "3.0") (projectile "2.9") (cl-lib "0.3")) 11 | 12 | ;; This file is NOT part of GNU Emacs. 13 | 14 | ;;; License: 15 | 16 | ;; This program is free software; you can redistribute it and/or modify 17 | ;; it under the terms of the GNU General Public License as published by 18 | ;; the Free Software Foundation; either version 3, or (at your option) 19 | ;; any later version. 20 | ;; 21 | ;; This program is distributed in the hope that it will be useful, 22 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | ;; GNU General Public License for more details. 25 | ;; 26 | ;; You should have received a copy of the GNU General Public License 27 | ;; along with GNU Emacs; see the file COPYING. If not, write to the 28 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 29 | ;; Boston, MA 02110-1301, USA. 30 | 31 | ;;; Commentary: 32 | ;; 33 | ;; This library provides easy project management and navigation. The 34 | ;; concept of a project is pretty basic - just a folder containing 35 | ;; special file. Currently git, mercurial and bazaar repos are 36 | ;; considered projects by default. If you want to mark a folder 37 | ;; manually as a project just create an empty .projectile file in 38 | ;; it. See the README for more details. 39 | ;; 40 | ;;; Code: 41 | 42 | ;; built-in libraries 43 | (require 'subr-x) 44 | (require 'cl-lib) 45 | (require 'grep) ;; TODO: Probably we should defer this require 46 | 47 | (require 'helm-core) 48 | (require 'helm-global-bindings) 49 | (require 'helm-types) 50 | (require 'helm-locate) 51 | (require 'helm-buffers) 52 | (require 'helm-files) 53 | 54 | (require 'projectile) 55 | 56 | (declare-function eshell "eshell") 57 | (declare-function helm-do-ag "ext:helm-ag") 58 | (declare-function dired-get-filename "dired") 59 | (defvar helm-ag-base-command) 60 | 61 | (defvar grep-find-ignored-directories) 62 | (defvar grep-find-ignored-files) 63 | 64 | (defgroup helm-projectile nil 65 | "Helm support for projectile." 66 | :prefix "helm-projectile-" 67 | :group 'projectile 68 | :group 'helm 69 | :link `(url-link :tag "GitHub" "https://github.com/bbatsov/helm-projectile")) 70 | 71 | (defvar helm-projectile-current-project-root) 72 | 73 | (defcustom helm-projectile-truncate-lines nil 74 | "Truncate lines in helm projectile commands when non--nil. 75 | 76 | Some `helm-projectile' commands have similar behavior with existing 77 | Helms. In these cases their respective custom var for truncation 78 | of lines will be honored. E.g. `helm-buffers-truncate-lines' 79 | dictates the truncation in `helm-projectile-switch-to-buffer'." 80 | :group 'helm-projectile 81 | :type 'boolean) 82 | 83 | ;;;###autoload 84 | (defcustom helm-projectile-fuzzy-match t 85 | "Enable fuzzy matching for Helm Projectile commands. 86 | This needs to be set before loading helm-projectile.el." 87 | :group 'helm-projectile 88 | :type 'boolean) 89 | 90 | (defmacro helm-projectile-define-key (keymap key def &rest bindings) 91 | "In KEYMAP, define KEY - DEF sequence KEY1 as DEF1, KEY2 as DEF2 ..." 92 | (declare (indent defun)) 93 | (let ((ret '(progn))) 94 | (while key 95 | (push 96 | `(define-key ,keymap ,key 97 | (lambda () 98 | (interactive) 99 | (helm-exit-and-execute-action ,def))) 100 | ret) 101 | (setq key (pop bindings) 102 | def (pop bindings))) 103 | (reverse ret))) 104 | 105 | (defun helm-projectile-hack-actions (actions &rest prescription) 106 | "Given a Helm action list and a prescription, return a hacked Helm action list. 107 | Optionally applies the PRESCRIPTION beforehand. 108 | 109 | The Helm action list ACTIONS is of the form: 110 | 111 | \(\(DESCRIPTION1 . FUNCTION1\) 112 | \(DESCRIPTION2 . FUNCTION2\) 113 | ... 114 | \(DESCRIPTIONn . FUNCTIONn\)\) 115 | 116 | PRESCRIPTION is in the form: 117 | 118 | \(INSTRUCTION1 INSTRUCTION2 ... INSTRUCTIONn\) 119 | 120 | If an INSTRUCTION is a symbol, the action with function name 121 | INSTRUCTION is deleted. 122 | 123 | If an INSTRUCTION is of the form \(FUNCTION1 . FUNCTION2\), the 124 | action with function name FUNCTION1 will change it's function to 125 | FUNCTION2. 126 | 127 | If an INSTRUCTION is of the form \(FUNCTION . DESCRIPTION\), and 128 | if an action with function name FUNCTION exists in the original 129 | Helm action list, the action in the Helm action list, with 130 | function name FUNCTION will change it's description to 131 | DESCRIPTION. Otherwise, (FUNCTION . DESCRIPTION) will be added to 132 | the action list. 133 | 134 | Please check out how `helm-projectile-file-actions' is defined 135 | for an example of how this function is being used." 136 | (let* ((to-delete (cl-remove-if (lambda (entry) (listp entry)) prescription)) 137 | (actions (cl-delete-if (lambda (action) (memq (cdr action) to-delete)) 138 | (copy-alist actions))) 139 | new) 140 | (cl-dolist (action actions) 141 | (when (setq new (cdr (assq (cdr action) prescription))) 142 | (if (stringp new) 143 | (setcar action new) 144 | (setcdr action new)))) 145 | ;; Add new actions from PRESCRIPTION 146 | (setq new nil) 147 | (cl-dolist (instruction prescription) 148 | (when (and (listp instruction) 149 | (null (rassq (car instruction) actions)) 150 | (symbolp (car instruction)) (stringp (cdr instruction))) 151 | (push (cons (cdr instruction) (car instruction)) new))) 152 | (append actions (nreverse new)))) 153 | 154 | (defun helm-projectile-vc (dir) 155 | "A Helm action for jumping to project root using `vc-dir' or Magit. 156 | DIR is a directory to be switched" 157 | (let ((projectile-require-project-root nil)) 158 | (projectile-vc dir))) 159 | 160 | (defun helm-projectile-compile-project (dir) 161 | "A Helm action for compile a project. 162 | DIR is the project root." 163 | (let ((helm--reading-passwd-or-string t) 164 | (default-directory dir)) 165 | (projectile-compile-project helm-current-prefix-arg))) 166 | 167 | (defun helm-projectile-test-project (dir) 168 | "A Helm action for test a project. 169 | DIR is the project root." 170 | (let ((helm--reading-passwd-or-string t) 171 | (default-directory dir)) 172 | (projectile-test-project helm-current-prefix-arg))) 173 | 174 | (defun helm-projectile-run-project (dir) 175 | "A Helm action for run a project. 176 | DIR is the project root." 177 | (let ((helm--reading-passwd-or-string t) 178 | (default-directory dir)) 179 | (projectile-run-project helm-current-prefix-arg))) 180 | 181 | (defun helm-projectile-remove-known-project (_ignore) 182 | "Remove selected projects from projectile project list. 183 | _IGNORE means the argument does not matter. 184 | It is there because Helm requires it." 185 | (let* ((projects (helm-marked-candidates :with-wildcard t)) 186 | (len (length projects))) 187 | (with-helm-display-marked-candidates 188 | helm-marked-buffer-name 189 | projects 190 | (if (not (y-or-n-p (format "Remove *%s projects(s)? " len))) 191 | (message "(No removal performed)") 192 | (progn 193 | (mapc (lambda (p) 194 | (setq projectile-known-projects (delete p projectile-known-projects))) 195 | projects) 196 | (projectile-save-known-projects)) 197 | (message "%s projects(s) removed" len))))) 198 | 199 | (defvar helm-projectile-projects-map 200 | (let ((map (make-sparse-keymap))) 201 | (set-keymap-parent map helm-map) 202 | (helm-projectile-define-key map 203 | (kbd "C-d") #'dired 204 | (kbd "M-g") #'helm-projectile-vc 205 | (kbd "M-e") #'helm-projectile-switch-to-shell 206 | (kbd "C-s") #'helm-projectile-grep 207 | (kbd "M-c") #'helm-projectile-compile-project 208 | (kbd "M-t") #'helm-projectile-test-project 209 | (kbd "M-r") #'helm-projectile-run-project 210 | (kbd "M-D") #'helm-projectile-remove-known-project) 211 | map) 212 | "Mapping for known projectile projects.") 213 | 214 | (defcustom helm-source-projectile-projects-actions 215 | (helm-make-actions 216 | "Switch to project" (lambda (project) 217 | (let ((projectile-completion-system 'helm)) 218 | (projectile-switch-project-by-name project))) 219 | "Open Dired in project's directory `C-d'" #'dired 220 | "Open project root in vc-dir or magit `M-g'" #'helm-projectile-vc 221 | "Switch to Eshell `M-e'" #'helm-projectile-switch-to-shell 222 | "Grep in projects `C-s'" #'helm-projectile-grep 223 | "Compile project `M-c'. With C-u, new compile command" #'helm-projectile-compile-project 224 | "Remove project(s) from project list `M-D'" #'helm-projectile-remove-known-project) 225 | "Actions for `helm-source-projectile-projects'." 226 | :group 'helm-projectile 227 | :type '(alist :key-type string :value-type function)) 228 | 229 | (defclass helm-projectile-projects-source (helm-source-sync helm-type-file) 230 | ((candidates :initform (lambda () (with-helm-current-buffer 231 | (mapcar #'copy-sequence 232 | (projectile-known-projects))))) 233 | (fuzzy-match :initform 'helm-projectile-fuzzy-match) 234 | (keymap :initform 'helm-projectile-projects-map) 235 | (mode-line :initform 'helm-read-file-name-mode-line-string) 236 | (action :initform 'helm-source-projectile-projects-actions)) 237 | "Helm source for known projectile projects.") 238 | 239 | (cl-defmethod helm--setup-source :after ((source helm-projectile-projects-source)) 240 | "Make SOURCE specific to project switching. 241 | The `helm-projectile-projects-source` inherits from 242 | `helm-type-file` (which see), which sets up actions, keymap, and 243 | help message slots to file specific ones. Override these slots 244 | to be specific to `helm-projectile-projects-source'." 245 | (setf (slot-value source 'action) 'helm-source-projectile-projects-actions) 246 | (setf (slot-value source 'keymap) helm-projectile-projects-map) 247 | ;; Use `ignore' as a persistent action, to actually keep `helm' session 248 | ;; when `helm-execute-persistent-action' is executed. 249 | (setf (slot-value source 'persistent-action) #'ignore) 250 | (let ((persistent-help "Do Nothing")) 251 | (setf (slot-value source 'persistent-help) persistent-help) 252 | (setf (slot-value source 'header-line) 253 | (helm-source--persistent-help-string 254 | persistent-help 255 | source))) 256 | (setf (slot-value source 'mode-line) 257 | (list "Project(s)" helm-mode-line-string))) 258 | 259 | (defvar helm-source-projectile-projects 260 | (helm-make-source "Projectile projects" 'helm-projectile-projects-source)) 261 | 262 | (defvar helm-projectile-dirty-projects-map 263 | (let ((map (make-sparse-keymap))) 264 | (set-keymap-parent map helm-map) 265 | (helm-projectile-define-key map 266 | (kbd "C-d") #'dired 267 | (kbd "M-o") #'(lambda (project) 268 | (let ((projectile-completion-system 'helm)) 269 | (projectile-switch-project-by-name project))) 270 | (kbd "M-e") #'helm-projectile-switch-to-shell 271 | (kbd "C-s") #'helm-projectile-grep 272 | (kbd "M-c") #'helm-projectile-compile-project 273 | (kbd "M-t") #'helm-projectile-test-project 274 | (kbd "M-r") #'helm-projectile-run-project 275 | (kbd "M-D") #'helm-projectile-remove-known-project) 276 | map) 277 | "Mapping for dirty projectile projects.") 278 | 279 | (defvar helm-source-projectile-dirty-projects 280 | (helm-build-sync-source "Projectile dirty projects" 281 | :candidates (lambda () (with-helm-current-buffer (helm-projectile-get-dirty-projects))) 282 | :fuzzy-match helm-projectile-fuzzy-match 283 | :keymap helm-projectile-dirty-projects-map 284 | :mode-line helm-read-file-name-mode-line-string 285 | :action '(("Open project root in vc-dir or magit" . helm-projectile-vc) 286 | ("Switch to project `M-o'" . 287 | (lambda (project) 288 | (let ((projectile-completion-system 'helm)) 289 | (projectile-switch-project-by-name project)))) 290 | ("Open Dired in project's directory `C-d'" . dired) 291 | ("Switch to Eshell `M-e'" . helm-projectile-switch-to-shell) 292 | ("Grep in projects `C-s'" . helm-projectile-grep) 293 | ("Compile project `M-c'. With C-u, new compile command" 294 | . helm-projectile-compile-project))) 295 | "Helm source for dirty version controlled projectile projects.") 296 | 297 | (defun helm-projectile-get-dirty-projects () 298 | "Return dirty version controlled known projects as an alist to 299 | have a nice display in Helm." 300 | (message "Checking for dirty known projects...") 301 | (let* ((status (projectile-check-vcs-status-of-known-projects)) 302 | (proj-dir (cl-loop for stat in status 303 | collect (car stat))) 304 | (status-display (cl-loop for stat in status collect 305 | (propertize (format "[%s]" (mapconcat 'identity (car (cdr stat)) ", ")) 'face 'helm-match))) 306 | (max-status-display-length (cl-loop for sd in status-display 307 | maximize (length sd))) 308 | (status-display (cl-loop for sd in status-display collect 309 | (format "%s%s " sd (make-string (- max-status-display-length (length sd)) ? )))) 310 | (full-display (cl-mapcar 'concat status-display proj-dir)) 311 | (helm-list (cl-pairlis full-display proj-dir))) 312 | helm-list)) 313 | 314 | (define-key helm-etags-map (kbd "C-c p f") 315 | (lambda () 316 | (interactive) 317 | (helm-run-after-exit 'helm-projectile-find-file nil))) 318 | 319 | (defun helm-projectile-file-persistent-action (candidate) 320 | "Persistent action for file-related functionality. 321 | 322 | Previews the contents of a file in a temporary buffer." 323 | (let ((buf (get-buffer-create " *helm-projectile persistent*"))) 324 | (cl-flet ((preview (candidate) 325 | (switch-to-buffer buf) 326 | (setq inhibit-read-only t) 327 | (erase-buffer) 328 | (insert-file-contents candidate) 329 | (let ((buffer-file-name candidate)) 330 | (set-auto-mode)) 331 | (font-lock-ensure) 332 | (setq inhibit-read-only nil))) 333 | (if (and (helm-get-attr 'previewp) 334 | (string= candidate (helm-get-attr 'current-candidate))) 335 | (progn 336 | (kill-buffer buf) 337 | (helm-set-attr 'previewp nil)) 338 | (preview candidate) 339 | (helm-set-attr 'previewp t))) 340 | (helm-set-attr 'current-candidate candidate))) 341 | 342 | (defun helm-projectile-find-files-eshell-command-on-file-action (candidate) 343 | (interactive) 344 | (let* ((helm-ff-default-directory (file-name-directory candidate))) 345 | (helm-find-files-eshell-command-on-file candidate))) 346 | 347 | (defun helm-projectile-ff-etags-select-action (candidate) 348 | (interactive) 349 | (let* ((helm-ff-default-directory (file-name-directory candidate))) 350 | (helm-ff-etags-select candidate))) 351 | 352 | (defun helm-projectile-switch-to-shell (dir) 353 | "Within DIR, switch to a shell corresponding to `helm-ff-preferred-shell-mode'." 354 | (interactive) 355 | (let* ((projectile-require-project-root nil) 356 | (helm-ff-default-directory (file-name-directory (projectile-expand-root dir)))) 357 | (helm-ff-switch-to-shell dir))) 358 | 359 | (defun helm-projectile-files-in-current-dired-buffer () 360 | "Return a list of files (only) in the current dired buffer." 361 | (let (flist) 362 | (cl-flet ((fpush (fname) (push fname flist))) 363 | (save-excursion 364 | (let (file buffer-read-only) 365 | (goto-char (point-min)) 366 | (while (not (eobp)) 367 | (save-excursion 368 | (and (not (eolp)) 369 | (setq file (dired-get-filename t t)) ; nil on non-file 370 | (progn (end-of-line) 371 | (funcall #'fpush file)))) 372 | (forward-line 1))))) 373 | (mapcar 'file-truename (nreverse flist)))) 374 | 375 | (defun helm-projectile-all-dired-buffers () 376 | "Get all current Dired buffers." 377 | (mapcar (lambda (b) 378 | (with-current-buffer b (buffer-name))) 379 | (cl-remove-if-not 380 | (lambda (b) 381 | (with-current-buffer b 382 | (and (eq major-mode 'dired-mode) 383 | (buffer-name)))) 384 | (buffer-list)))) 385 | 386 | (defvar helm-projectile-virtual-dired-remote-enable nil 387 | "Enable virtual Dired manager on remote host. 388 | Disabled by default.") 389 | 390 | (defun helm-projectile-dired-files-new-action (candidate) 391 | "Create a Dired buffer from chosen files. 392 | CANDIDATE is the selected file, but choose the marked files if available." 393 | (if (and (file-remote-p (projectile-project-root)) 394 | (not helm-projectile-virtual-dired-remote-enable)) 395 | (message "Virtual Dired manager is disabled in remote host. Enable with %s." 396 | (propertize "helm-projectile-virtual-dired-remote-enable" 'face 'font-lock-keyword-face)) 397 | (let ((files (cl-remove-if-not 398 | (lambda (f) 399 | (not (string= f ""))) 400 | (mapcar (lambda (file) 401 | (replace-regexp-in-string (projectile-project-root) "" file)) 402 | (helm-marked-candidates :with-wildcard t)))) 403 | (new-name (completing-read "Select or enter a new buffer name: " 404 | (helm-projectile-all-dired-buffers))) 405 | (helm--reading-passwd-or-string t) 406 | (default-directory (projectile-project-root))) 407 | ;; create a unique buffer that is unique to any directory in default-directory 408 | ;; or opened buffer; when Dired is passed with a non-existence directory name, 409 | ;; it only creates a buffer and insert everything. If a new name user supplied 410 | ;; exists as default-directory, Dired throws error when insert anything that 411 | ;; does not exist in current directory. 412 | (with-current-buffer (dired (cons (make-temp-name new-name) 413 | (if files 414 | files 415 | (list candidate)))) 416 | (when (get-buffer new-name) 417 | (kill-buffer new-name)) 418 | (rename-buffer new-name))))) 419 | 420 | (defun helm-projectile-dired-files-add-action (candidate) 421 | "Add files to a Dired buffer. 422 | CANDIDATE is the selected file. Used when no file is explicitly marked." 423 | (if (and (file-remote-p (projectile-project-root)) 424 | (not helm-projectile-virtual-dired-remote-enable)) 425 | (message "Virtual Dired manager is disabled in remote host. Enable with %s." 426 | (propertize "helm-projectile-virtual-dired-remote-enable" 'face 'font-lock-keyword-face)) 427 | (if (eq (with-helm-current-buffer major-mode) 'dired-mode) 428 | (let* ((marked-files (helm-marked-candidates :with-wildcard t)) 429 | (helm--reading-passwd-or-string t) 430 | (root (projectile-project-root)) ; store root for later use 431 | (dired-buffer-name (or (and (eq major-mode 'dired-mode) (buffer-name)) 432 | (completing-read "Select a Dired buffer:" 433 | (helm-projectile-all-dired-buffers)))) 434 | (dired-files (with-current-buffer dired-buffer-name 435 | (helm-projectile-files-in-current-dired-buffer))) 436 | (files (sort (mapcar (lambda (file) 437 | (replace-regexp-in-string (projectile-project-root) "" file)) 438 | (cl-nunion (if marked-files 439 | marked-files 440 | (list candidate)) 441 | dired-files 442 | :test #'string-equal)) 443 | 'string-lessp))) 444 | (kill-buffer dired-buffer-name) 445 | ;; Rebind default-directory because after killing a buffer, we 446 | ;; could be in any buffer and default-directory is set to that 447 | ;; random buffer 448 | ;; 449 | ;; Also use saved root directory, because after killing a buffer, 450 | ;; we could be outside of current project 451 | (let ((default-directory root)) 452 | (with-current-buffer (dired (cons (make-temp-name dired-buffer-name) 453 | (if files 454 | (mapcar (lambda (file) 455 | (replace-regexp-in-string root "" file)) 456 | files) 457 | (list candidate)))) 458 | (rename-buffer dired-buffer-name)))) 459 | (error "You're not in a Dired buffer to add")))) 460 | 461 | (defun helm-projectile-dired-files-delete-action (candidate) 462 | "Delete selected entries from a Dired buffer. 463 | CANDIDATE is the selected file. Used when no file is explicitly marked." 464 | (if (and (file-remote-p (projectile-project-root)) 465 | (not helm-projectile-virtual-dired-remote-enable)) 466 | (message "Virtual Dired manager is disabled in remote host. Enable with %s." 467 | (propertize "helm-projectile-virtual-dired-remote-enable" 'face 'font-lock-keyword-face)) 468 | (let* ((helm--reading-passwd-or-string t) 469 | (root (projectile-project-root)) 470 | (dired-buffer-name (with-helm-current-buffer (buffer-name))) 471 | (dired-files (with-current-buffer dired-buffer-name 472 | (helm-projectile-files-in-current-dired-buffer))) 473 | (files (sort (cl-set-exclusive-or (helm-marked-candidates :with-wildcard t) 474 | dired-files 475 | :test #'string-equal) #'string-lessp))) 476 | (kill-buffer dired-buffer-name) 477 | ;; similar reason to `helm-projectile-dired-files-add-action' 478 | (let ((default-directory root)) 479 | (with-current-buffer (dired (cons (make-temp-name dired-buffer-name) 480 | (if files 481 | (mapcar (lambda (file) 482 | (replace-regexp-in-string root "" file)) 483 | files) 484 | (list candidate)))) 485 | (rename-buffer dired-buffer-name)))))) 486 | 487 | (defun helm-projectile-run-projectile-hooks-after-find-file (_orig-fun &rest _args) 488 | "Run `projectile-find-file-hook' if using projectile." 489 | (when (and projectile-mode (projectile-project-p)) 490 | (run-hooks 'projectile-find-file-hook))) 491 | 492 | (advice-add 'helm-find-file-or-marked 493 | :after #'helm-projectile-run-projectile-hooks-after-find-file) 494 | 495 | (defvar helm-projectile-find-file-map 496 | (let ((map (copy-keymap helm-find-files-map))) 497 | (helm-projectile-define-key map 498 | (kbd "C-c f") #'helm-projectile-dired-files-new-action 499 | (kbd "C-c a") #'helm-projectile-dired-files-add-action 500 | (kbd "M-e") #'helm-projectile-switch-to-shell 501 | (kbd "M-.") #'helm-projectile-ff-etags-select-action 502 | (kbd "M-!") #'helm-projectile-find-files-eshell-command-on-file-action) 503 | (define-key map (kbd "") #'helm-previous-source) 504 | (define-key map (kbd "") #'helm-next-source) 505 | (dolist (cmd '(helm-find-files-up-one-level 506 | helm-find-files-down-last-level)) 507 | (substitute-key-definition cmd nil map)) 508 | map) 509 | "Mapping for file commands in Helm Projectile.") 510 | 511 | (defvar helm-projectile-file-actions 512 | (helm-projectile-hack-actions 513 | helm-find-files-actions 514 | ;; Delete these actions 515 | 'helm-ff-browse-project 516 | 'helm-insert-file-name-completion-at-point 517 | 'helm-ff-find-sh-command 518 | 'helm-ff-cache-add-file 519 | ;; Substitute these actions 520 | '(helm-ff-switch-to-shell . helm-projectile-switch-to-shell) 521 | '(helm-ff-etags-select . helm-projectile-ff-etags-select-action) 522 | '(helm-find-files-eshell-command-on-file 523 | . helm-projectile-find-files-eshell-command-on-file-action) 524 | ;; Change action descriptions 525 | '(helm-find-file-as-root . "Find file as root `C-c r'") 526 | ;; New actions 527 | '(helm-projectile-dired-files-new-action 528 | . "Create Dired buffer from files `C-c f'") 529 | '(helm-projectile-dired-files-add-action 530 | . "Add files to Dired buffer `C-c a'")) 531 | "Action for files.") 532 | 533 | (defun helm-projectile--move-selection-p (selection) 534 | "Return non-nil if should move Helm selector after SELECTION. 535 | 536 | SELECTION should be moved unless it's one of: 537 | 538 | - Non-string 539 | - Existing file 540 | - Non-remote file that matches `helm-tramp-file-name-regexp'" 541 | (not (or (not (stringp selection)) 542 | (file-exists-p selection) 543 | (and (string-match helm-tramp-file-name-regexp selection) 544 | (not (file-remote-p selection nil t)))))) 545 | 546 | (defun helm-projectile--move-to-real () 547 | "Move to first real candidate. 548 | 549 | Similar to `helm-ff--move-to-first-real-candidate', but without 550 | unnecessary complexity." 551 | (while (let* ((src (helm-get-current-source)) 552 | (selection (and (not (helm-empty-source-p)) 553 | (helm-get-selection nil nil src)))) 554 | (and (not (helm-end-of-source-p)) 555 | (helm-projectile--move-selection-p selection))) 556 | (helm-next-line))) 557 | 558 | (defun helm-projectile--remove-move-to-real () 559 | "Hook function to remove `helm-projectile--move-to-real'. 560 | 561 | Meant to be added to `helm-cleanup-hook', from which it removes 562 | itself at the end." 563 | (remove-hook 'helm-after-update-hook #'helm-projectile--move-to-real) 564 | (remove-hook 'helm-cleanup-hook #'helm-projectile--remove-move-to-real)) 565 | 566 | (defvar helm-source-projectile-files-list-before-init-hook 567 | (lambda () 568 | (add-hook 'helm-after-update-hook #'helm-projectile--move-to-real) 569 | (add-hook 'helm-cleanup-hook #'helm-projectile--remove-move-to-real))) 570 | 571 | (defvar helm-source-projectile-files-list 572 | (helm-build-sync-source "Projectile files" 573 | :before-init-hook 'helm-source-projectile-files-list-before-init-hook 574 | :candidates (lambda () 575 | (when (projectile-project-p) 576 | (with-helm-current-buffer 577 | (cl-loop with root = (projectile-project-root) 578 | for display in (projectile-current-project-files) 579 | collect (cons display (expand-file-name display root)))))) 580 | :filtered-candidate-transformer 581 | (lambda (files _source) 582 | (with-helm-current-buffer 583 | (let* ((root (projectile-project-root)) 584 | (file-at-root (file-relative-name (expand-file-name helm-pattern root)))) 585 | (if (or (string-empty-p helm-pattern) 586 | (assoc helm-pattern files)) 587 | files 588 | (if (equal helm-pattern file-at-root) 589 | (cl-acons (helm-ff-prefix-filename helm-pattern nil t) 590 | (expand-file-name helm-pattern) 591 | files) 592 | (cl-pairlis (list (helm-ff-prefix-filename helm-pattern nil t) 593 | (helm-ff-prefix-filename file-at-root nil t)) 594 | (list (expand-file-name helm-pattern) 595 | (expand-file-name helm-pattern root)) 596 | files)))))) 597 | :fuzzy-match helm-projectile-fuzzy-match 598 | :keymap helm-projectile-find-file-map 599 | :help-message 'helm-ff-help-message 600 | :mode-line helm-read-file-name-mode-line-string 601 | :action helm-projectile-file-actions 602 | :persistent-action #'helm-projectile-file-persistent-action 603 | :persistent-help "Preview file") 604 | "Helm source definition for Projectile files.") 605 | 606 | (defvar helm-source-projectile-files-in-all-projects-list 607 | (helm-build-sync-source "Projectile files in all Projects" 608 | :candidates (lambda () 609 | (with-helm-current-buffer 610 | (let ((projectile-require-project-root nil)) 611 | (projectile-all-project-files)))) 612 | :keymap helm-projectile-find-file-map 613 | :help-message 'helm-ff-help-message 614 | :mode-line helm-read-file-name-mode-line-string 615 | :action helm-projectile-file-actions 616 | :persistent-action #'helm-projectile-file-persistent-action 617 | :persistent-help "Preview file") 618 | "Helm source definition for all Projectile files in all projects.") 619 | 620 | (defvar helm-projectile-dired-file-actions 621 | (helm-projectile-hack-actions 622 | helm-projectile-file-actions 623 | ;; New actions 624 | '(helm-projectile-dired-files-delete-action . "Remove entry(s) from Dired buffer `C-c d'"))) 625 | 626 | (defvar helm-source-projectile-dired-files-list 627 | (helm-build-in-buffer-source "Projectile files in current Dired buffer" 628 | :data (lambda () 629 | (if (and (file-remote-p (projectile-project-root)) 630 | (not helm-projectile-virtual-dired-remote-enable)) 631 | nil 632 | (when (eq major-mode 'dired-mode) 633 | (helm-projectile-files-in-current-dired-buffer)))) 634 | :filter-one-by-one (lambda (file) 635 | (let ((helm-ff-transformer-show-only-basename t)) 636 | (helm-ff-filter-candidate-one-by-one file))) 637 | :action-transformer 'helm-find-files-action-transformer 638 | :keymap (let ((map (copy-keymap helm-projectile-find-file-map))) 639 | (helm-projectile-define-key map 640 | (kbd "C-c d") 'helm-projectile-dired-files-delete-action) 641 | map) 642 | :help-message 'helm-ff-help-message 643 | :mode-line helm-read-file-name-mode-line-string 644 | :action helm-projectile-dired-file-actions) 645 | "Helm source definition for Projectile delete files.") 646 | 647 | (defun helm-projectile-dired-find-dir (dir) 648 | "Jump to a selected directory DIR from `helm-projectile'." 649 | (dired (expand-file-name dir (projectile-project-root))) 650 | (run-hooks 'projectile-find-dir-hook)) 651 | 652 | (defun helm-projectile-dired-find-dir-other-window (dir) 653 | "Jump to a selected directory DIR from `helm-projectile'." 654 | (dired-other-window (expand-file-name dir (projectile-project-root))) 655 | (run-hooks 'projectile-find-dir-hook)) 656 | 657 | (defvar helm-source-projectile-directories-list 658 | (helm-build-sync-source "Projectile directories" 659 | :candidates (lambda () 660 | (when (projectile-project-p) 661 | (with-helm-current-buffer 662 | (let ((dirs (if projectile-find-dir-includes-top-level 663 | (append '("./") (projectile-current-project-dirs)) 664 | (projectile-current-project-dirs)))) 665 | (helm-projectile--files-display-real dirs (projectile-project-root)))))) 666 | :fuzzy-match helm-projectile-fuzzy-match 667 | :action-transformer 'helm-find-files-action-transformer 668 | :keymap (let ((map (make-sparse-keymap))) 669 | (set-keymap-parent map helm-map) 670 | (helm-projectile-define-key map 671 | (kbd "") #'helm-previous-source 672 | (kbd "") #'helm-next-source 673 | (kbd "C-c o") #'helm-projectile-dired-find-dir-other-window 674 | (kbd "M-e") #'helm-projectile-switch-to-shell 675 | (kbd "C-c f") #'helm-projectile-dired-files-new-action 676 | (kbd "C-c a") #'helm-projectile-dired-files-add-action 677 | (kbd "C-s") #'helm-projectile-grep) 678 | map) 679 | :help-message 'helm-ff-help-message 680 | :mode-line helm-read-file-name-mode-line-string 681 | :action '(("Open Dired" . helm-projectile-dired-find-dir) 682 | ("Open Dired in other window `C-c o'" . helm-projectile-dired-find-dir) 683 | ("Switch to Eshell `M-e'" . helm-projectile-switch-to-shell) 684 | ("Grep in projects `C-s'" . helm-projectile-grep) 685 | ("Create Dired buffer from files `C-c f'" . helm-projectile-dired-files-new-action) 686 | ("Add files to Dired buffer `C-c a'" . helm-projectile-dired-files-add-action))) 687 | "Helm source for listing project directories.") 688 | 689 | (defvar helm-projectile-buffers-list-cache nil) 690 | 691 | (defclass helm-source-projectile-buffer (helm-source-sync helm-type-buffer) 692 | ((init :initform (lambda () 693 | ;; Issue #51 Create the list before `helm-buffer' creation. 694 | (setq helm-projectile-buffers-list-cache 695 | (ignore-errors (remove (buffer-name) (projectile-project-buffer-names)))) 696 | (let ((result (cl-loop for b in helm-projectile-buffers-list-cache 697 | maximize (length b) into len-buf 698 | maximize (length (with-current-buffer b 699 | (symbol-name major-mode))) 700 | into len-mode 701 | finally return (cons len-buf len-mode)))) 702 | (unless helm-buffer-max-length 703 | (setq helm-buffer-max-length (car result))) 704 | (unless helm-buffer-max-len-mode 705 | ;; If a new buffer is longer that this value 706 | ;; this value will be updated 707 | (setq helm-buffer-max-len-mode (cdr result)))))) 708 | (candidates :initform 'helm-projectile-buffers-list-cache) 709 | (matchplugin :initform nil) 710 | (match :initform 'helm-buffers-match-function) 711 | (persistent-action :initform 'helm-buffers-list-persistent-action) 712 | (keymap :initform 'helm-buffer-map) 713 | (volatile :initform t) 714 | (persistent-help 715 | :initform 716 | "Show this buffer / C-u \\[helm-execute-persistent-action]: Kill this buffer"))) 717 | 718 | (defvar helm-source-projectile-buffers-list (helm-make-source "Project buffers" 'helm-source-projectile-buffer)) 719 | 720 | (defvar helm-source-projectile-recentf-list 721 | (helm-build-sync-source "Projectile recent files" 722 | :candidates (lambda () 723 | (when (projectile-project-p) 724 | (with-helm-current-buffer 725 | (helm-projectile--files-display-real (projectile-recentf-files) 726 | (projectile-project-root))))) 727 | :fuzzy-match helm-projectile-fuzzy-match 728 | :keymap helm-projectile-find-file-map 729 | :help-message 'helm-ff-help-message 730 | :mode-line helm-read-file-name-mode-line-string 731 | :action helm-projectile-file-actions 732 | :persistent-action #'helm-projectile-file-persistent-action 733 | :persistent-help "Preview file") 734 | "Helm source definition for recent files in current project.") 735 | 736 | (defvar helm-source-projectile-files-and-dired-list 737 | '(helm-source-projectile-dired-files-list 738 | helm-source-projectile-files-list)) 739 | 740 | (defvar helm-source-projectile-directories-and-dired-list 741 | '(helm-source-projectile-dired-files-list 742 | helm-source-projectile-directories-list)) 743 | 744 | (defcustom helm-projectile-git-grep-command 745 | "git --no-pager grep --no-color -n%c -e %p -- %f" 746 | "Command to execute when performing `helm-grep' inside a projectile git project. 747 | See documentation of `helm-grep-default-command' for the format." 748 | :type 'string 749 | :group 'helm-projectile 750 | ) 751 | 752 | (defcustom helm-projectile-grep-command 753 | "grep -a -r %e -n%cH -e %p %f ." 754 | "Command to execute when performing `helm-grep' outside a projectile git project. 755 | See documentation of `helm-grep-default-command' for the format." 756 | :type 'string 757 | :group 'helm-projectile 758 | ) 759 | 760 | 761 | (defcustom helm-projectile-sources-list 762 | '(helm-source-projectile-buffers-list 763 | helm-source-projectile-files-list 764 | helm-source-projectile-projects) 765 | "Default sources for `helm-projectile'." 766 | :type '(repeat symbol) 767 | :group 'helm-projectile) 768 | 769 | (defmacro helm-projectile-command (command source prompt &optional not-require-root truncate-lines-var) 770 | "Template for generic `helm-projectile' commands. 771 | COMMAND is a command name to be appended with \"helm-projectile\" prefix. 772 | SOURCE is a Helm source that should be Projectile specific. 773 | PROMPT is a string for displaying as a prompt. 774 | NOT-REQUIRE-ROOT specifies the command doesn't need to be used in a 775 | project root. 776 | TRUNCATE-LINES-VAR is the symbol used dictate truncation of lines. 777 | Defaults is `helm-projectile-truncate-lines'." 778 | (unless truncate-lines-var (setq truncate-lines-var 'helm-projectile-truncate-lines)) 779 | `(defun ,(intern (concat "helm-projectile-" command)) (&optional arg) 780 | "Use projectile with Helm for finding files in project 781 | 782 | With a prefix ARG invalidates the cache first." 783 | (interactive "P") 784 | (if (projectile-project-p) 785 | (projectile-maybe-invalidate-cache arg) 786 | (unless ,not-require-root 787 | (error "You're not in a project"))) 788 | (let ((helm-ff-transformer-show-only-basename nil) 789 | ;; for consistency, we should just let Projectile take care of ignored files 790 | (helm-boring-file-regexp-list nil)) 791 | (helm :sources ,source 792 | :buffer (concat "*helm projectile: " (projectile-project-name) "*") 793 | :truncate-lines ,truncate-lines-var 794 | :prompt (projectile-prepend-project-name ,prompt))))) 795 | 796 | (helm-projectile-command "switch-project" 'helm-source-projectile-projects "Switch to project: " t) 797 | (helm-projectile-command "find-file" helm-source-projectile-files-and-dired-list "Find file: ") 798 | (helm-projectile-command "find-file-in-known-projects" 'helm-source-projectile-files-in-all-projects-list "Find file in projects: " t) 799 | (helm-projectile-command "find-dir" helm-source-projectile-directories-and-dired-list "Find dir: ") 800 | (helm-projectile-command "recentf" 'helm-source-projectile-recentf-list "Recently visited file: ") 801 | (helm-projectile-command "switch-to-buffer" 'helm-source-projectile-buffers-list "Switch to buffer: " nil helm-buffers-truncate-lines) 802 | (helm-projectile-command "browse-dirty-projects" 'helm-source-projectile-dirty-projects "Select a project: " t) 803 | 804 | (defun helm-projectile--files-display-real (files root) 805 | "Create (DISPLAY . REAL) pairs with FILES and ROOT. 806 | 807 | DISPLAY is the short file name. REAL is the full path." 808 | (cl-loop for display in files 809 | collect (cons display (expand-file-name display root)))) 810 | 811 | ;;;###autoload 812 | (defun helm-projectile-find-file-dwim () 813 | "Find file at point based on context." 814 | (interactive) 815 | (let* ((project-root (projectile-project-root)) 816 | (project-files (projectile-current-project-files)) 817 | (files (projectile-select-files project-files))) 818 | (if (= (length files) 1) 819 | (find-file (expand-file-name (car files) (projectile-project-root))) 820 | (helm :sources (helm-build-sync-source "Projectile files" 821 | :candidates (if (> (length files) 1) 822 | (helm-projectile--files-display-real files project-root) 823 | (helm-projectile--files-display-real project-files project-root)) 824 | :fuzzy-match helm-projectile-fuzzy-match 825 | :action-transformer 'helm-find-files-action-transformer 826 | :keymap helm-projectile-find-file-map 827 | :help-message helm-ff-help-message 828 | :mode-line helm-read-file-name-mode-line-string 829 | :action helm-projectile-file-actions 830 | :persistent-action #'helm-projectile-file-persistent-action 831 | :persistent-help "Preview file") 832 | :buffer "*helm projectile*" 833 | :truncate-lines helm-projectile-truncate-lines 834 | :prompt (projectile-prepend-project-name "Find file: "))))) 835 | 836 | ;;;###autoload 837 | (defun helm-projectile-find-other-file (&optional flex-matching) 838 | "Switch between files with the same name but different extensions using Helm. 839 | With FLEX-MATCHING, match any file that contains the base name of 840 | current file. Other file extensions can be customized with the 841 | variable `projectile-other-file-alist'." 842 | (interactive "P") 843 | (let* ((project-root (projectile-project-root)) 844 | (other-files (projectile-get-other-files (buffer-file-name) 845 | flex-matching))) 846 | (if other-files 847 | (if (= (length other-files) 1) 848 | (find-file (expand-file-name (car other-files) project-root)) 849 | (progn 850 | (let* ((helm-ff-transformer-show-only-basename nil)) 851 | (helm :sources (helm-build-sync-source "Projectile other files" 852 | :candidates (helm-projectile--files-display-real other-files project-root) 853 | :keymap helm-projectile-find-file-map 854 | :help-message helm-ff-help-message 855 | :mode-line helm-read-file-name-mode-line-string 856 | :action helm-projectile-file-actions 857 | :persistent-action #'helm-projectile-file-persistent-action 858 | :persistent-help "Preview file") 859 | :buffer "*helm projectile*" 860 | :truncate-lines helm-projectile-truncate-lines 861 | :prompt (projectile-prepend-project-name "Find other file: "))))) 862 | (error "No other file found")))) 863 | 864 | (defcustom helm-projectile-ignore-strategy 'projectile 865 | "Allow projectile to compute ignored files and directories. 866 | 867 | When set to `projectile', the package will compute ignores and 868 | explicitely add additionally command line arguments to the search 869 | tool. Note that this might override search tool specific 870 | behaviors (for instance ag would not use VCS ignore files). 871 | 872 | When set to `search-tool', the above does not happen." 873 | :group 'helm-projectile 874 | :type '(choice (const :tag "Allow projectile to compute ignores" projectile) 875 | (const :tag "Let the search tool compute ignores" search-tool))) 876 | 877 | (defun helm-projectile--projectile-ignore-strategy () 878 | "True if the ignore strategy is `projectile'." 879 | (eq 'projectile helm-projectile-ignore-strategy)) 880 | 881 | (defun helm-projectile--ignored-files () 882 | "Compute ignored files." 883 | (cl-union (projectile-ignored-files-rel) grep-find-ignored-files)) 884 | 885 | (defun helm-projectile--ignored-directories () 886 | "Compute ignored directories." 887 | (cl-union (projectile-ignored-directories-rel) grep-find-ignored-directories)) 888 | 889 | (defcustom helm-projectile-grep-or-ack-actions 890 | '("Find file" helm-grep-action 891 | "Find file other frame" helm-grep-other-frame 892 | (lambda () (and (locate-library "elscreen") 893 | "Find file in Elscreen")) 894 | helm-grep-jump-elscreen 895 | "Save results in grep buffer" helm-grep-save-results 896 | "Find file other window" helm-grep-other-window) 897 | "Available actions for `helm-projectile-grep-or-ack'. 898 | The contents of this list are passed as the arguments to `helm-make-actions'." 899 | :type 'symbol 900 | :group 'helm-projectile) 901 | 902 | (defcustom helm-projectile-set-input-automatically t 903 | "If non-nil, attempt to set search input automatically. 904 | Automatic input selection uses the region (if there is an active 905 | region), otherwise it uses the current symbol at point (if there 906 | is one). Applies to `helm-projectile-grep' and 907 | `helm-projectile-ack' only. If the `helm-ag' package is 908 | installed, then automatic input behavior for `helm-projectile-ag' 909 | can be customized using `helm-ag-insert-at-point'." 910 | :group 'helm-projectile 911 | :type 'boolean) 912 | 913 | (defun helm-projectile-grep-or-ack (&optional dir use-ack-p ack-ignored-pattern ack-executable) 914 | "Perform helm-grep at project root. 915 | DIR directory where to search 916 | USE-ACK-P indicates whether to use ack or not. 917 | ACK-IGNORED-PATTERN is a file regex to exclude from searching. 918 | ACK-EXECUTABLE is the actual ack binary name. 919 | It is usually \"ack\" or \"ack-grep\". 920 | If it is nil, or ack/ack-grep not found then use default grep command." 921 | (when (helm-projectile--projectile-ignore-strategy) 922 | (setq helm-grep-ignored-files (helm-projectile--ignored-files) 923 | helm-grep-ignored-directories (mapcar 'directory-file-name (helm-projectile--ignored-directories)))) 924 | 925 | (let* ((default-directory (or dir (projectile-project-root))) 926 | (helm-ff-default-directory default-directory) 927 | (helm-grep-in-recurse t) 928 | (helm-grep-default-command (if use-ack-p 929 | (concat ack-executable " -H --no-group --no-color " 930 | (when ack-ignored-pattern (concat ack-ignored-pattern " ")) 931 | "%p %f") 932 | (if (and projectile-use-git-grep (eq (projectile-project-vcs) 'git)) 933 | helm-projectile-git-grep-command 934 | helm-projectile-grep-command))) 935 | (helm-grep-default-recurse-command helm-grep-default-command)) 936 | 937 | (setq helm-source-grep 938 | (helm-build-async-source 939 | (capitalize (helm-grep-command t)) 940 | :header-name (lambda (_name) 941 | (let ((name (if use-ack-p 942 | "Helm Projectile Ack" 943 | "Helm Projectile Grep"))) 944 | (concat name " " "(C-c ? Help)"))) 945 | :candidates-process 'helm-grep-collect-candidates 946 | :filter-one-by-one 'helm-grep-filter-one-by-one 947 | :candidate-number-limit 9999 948 | :nohighlight t 949 | ;; We need to specify keymap here and as :keymap arg [1] 950 | ;; to make it available in further resuming. 951 | :keymap helm-grep-map 952 | :history 'helm-grep-history 953 | :action (apply #'helm-make-actions helm-projectile-grep-or-ack-actions) 954 | :persistent-action 'helm-grep-persistent-action 955 | :persistent-help "Jump to line (`C-u' Record in mark ring)" 956 | :requires-pattern 2)) 957 | (helm 958 | :sources 'helm-source-grep 959 | :input (when helm-projectile-set-input-automatically 960 | (if (region-active-p) 961 | (buffer-substring-no-properties (region-beginning) (region-end)) 962 | (thing-at-point 'symbol))) 963 | :buffer (format "*helm %s*" (if use-ack-p 964 | "ack" 965 | "grep")) 966 | :default-directory default-directory 967 | :keymap helm-grep-map 968 | :history 'helm-grep-history 969 | :truncate-lines helm-grep-truncate-lines))) 970 | 971 | ;;;###autoload 972 | (defun helm-projectile-on () 973 | "Turn on `helm-projectile' key bindings." 974 | (interactive) 975 | (message "Turn on helm-projectile key bindings") 976 | (helm-projectile-toggle 1)) 977 | 978 | ;;;###autoload 979 | (defun helm-projectile-off () 980 | "Turn off `helm-projectile' key bindings." 981 | (interactive) 982 | (message "Turn off helm-projectile key bindings") 983 | (helm-projectile-toggle -1)) 984 | 985 | ;;;###autoload 986 | (defun helm-projectile-grep (&optional dir) 987 | "Helm version of `projectile-grep'. 988 | DIR is the project root, if not set then current directory is used" 989 | (interactive) 990 | (let ((project-root (or dir (projectile-project-root) (error "You're not in a project")))) 991 | (funcall 'run-with-timer 0.01 nil 992 | #'helm-projectile-grep-or-ack project-root nil))) 993 | 994 | ;;;###autoload 995 | (defun helm-projectile-ack (&optional dir) 996 | "Helm version of projectile-ack. 997 | DIR directory where to search" 998 | (interactive) 999 | (let* ((project-root (or dir (projectile-project-root) (error "You're not in a project"))) 1000 | (ignored (when (helm-projectile--projectile-ignore-strategy) 1001 | (mapconcat 1002 | 'identity 1003 | (cl-union (mapcar (lambda (path) 1004 | (concat "--ignore-dir=" (file-name-nondirectory (directory-file-name path)))) 1005 | (helm-projectile--ignored-directories)) 1006 | (mapcar (lambda (path) 1007 | (concat "--ignore-file=match:" (shell-quote-argument path))) 1008 | (append (helm-projectile--ignored-files) 1009 | (projectile-patterns-to-ignore)))) 1010 | " "))) 1011 | (helm-ack-grep-executable (cond 1012 | ((executable-find "ack") "ack") 1013 | ((executable-find "ack-grep") "ack-grep") 1014 | (t (error "ack or ack-grep is not available"))))) 1015 | (funcall 'run-with-timer 0.01 nil 1016 | #'helm-projectile-grep-or-ack project-root t ignored helm-ack-grep-executable))) 1017 | 1018 | ;;;###autoload 1019 | 1020 | (defun helm-projectile-ag (&optional options) 1021 | "Helm version of `projectile-ag'. 1022 | OPTIONS explicit command line arguments to ag" 1023 | (interactive (if current-prefix-arg (list (helm-read-string "option: " "" 'helm-ag--extra-options-history)))) 1024 | (if (require 'helm-ag nil t) 1025 | (if (projectile-project-p) 1026 | (progn 1027 | (when (helm-projectile--projectile-ignore-strategy) 1028 | (setq grep-find-ignored-files (helm-projectile--ignored-files) 1029 | grep-find-ignored-directories (helm-projectile--ignored-directories))) 1030 | (let* ((ignored (when (helm-projectile--projectile-ignore-strategy) 1031 | (mapconcat (lambda (i) 1032 | (concat "--ignore " i)) 1033 | (append grep-find-ignored-files 1034 | grep-find-ignored-directories 1035 | (cadr (projectile-parse-dirconfig-file))) 1036 | " "))) 1037 | (helm-ag-base-command (concat helm-ag-base-command 1038 | (when ignored (concat " " ignored)) 1039 | " " options)) 1040 | (current-prefix-arg nil)) 1041 | (helm-do-ag (projectile-project-root) (car (projectile-parse-dirconfig-file))))) 1042 | (error "You're not in a project")) 1043 | (when (yes-or-no-p "`helm-ag' is not installed. Install? ") 1044 | (condition-case nil 1045 | (progn 1046 | (package-install 'helm-ag) 1047 | (helm-projectile-ag options)) 1048 | (error (error "`helm-ag' is not available. Is MELPA in your `package-archives'?")))))) 1049 | 1050 | ;; Declare/define these to satisfy the byte compiler 1051 | (defvar helm-rg-prepend-file-name-line-at-top-of-matches) 1052 | (defvar helm-rg-include-file-on-every-match-line) 1053 | (declare-function helm-rg "ext:helm-rg") 1054 | (declare-function helm-rg--get-thing-at-pt "ext:helm-rg") 1055 | 1056 | (defun helm-projectile-rg--region-selection () 1057 | (when helm-projectile-set-input-automatically 1058 | (if (region-active-p) 1059 | (buffer-substring-no-properties (region-beginning) (region-end)) 1060 | (helm-rg--get-thing-at-pt)))) 1061 | 1062 | (defun glob-quote (string) 1063 | "Quote the special glob characters: *, ?, [, and ]. 1064 | STRING the string in which to escape special characters." 1065 | (replace-regexp-in-string "[]*?[]" "\\\\\\&" string)) 1066 | 1067 | ;;;###autoload 1068 | (defun helm-projectile-rg () 1069 | "Projectile version of `helm-rg'." 1070 | (interactive) 1071 | (if (require 'helm-rg nil t) 1072 | (if (projectile-project-p) 1073 | (let* ((helm-rg-prepend-file-name-line-at-top-of-matches nil) 1074 | (helm-rg-include-file-on-every-match-line t) 1075 | (default-directory (projectile-project-root))) 1076 | (when (helm-projectile--projectile-ignore-strategy) 1077 | (setq helm-rg--extra-args 1078 | (mapconcat 1079 | 'identity 1080 | (cl-union (mapcan (lambda (path) 1081 | (list "--glob" (concat "!" (glob-quote path)))) 1082 | (helm-projectile--ignored-files)) 1083 | (mapcan (lambda (path) 1084 | (list "--glob" (concat "!" (glob-quote path) "/**"))) 1085 | (mapcar 'directory-file-name (helm-projectile--ignored-directories)))) 1086 | " "))) 1087 | (helm-rg (helm-projectile-rg--region-selection) 1088 | nil)) 1089 | (error "You're not in a project")) 1090 | (when (yes-or-no-p "`helm-rg' is not installed. Install? ") 1091 | (condition-case nil 1092 | (progn 1093 | (package-install 'helm-rg) 1094 | (helm-projectile-rg)) 1095 | (error "`helm-rg' is not available. Is MELPA in your `package-archives'?"))))) 1096 | 1097 | (defun helm-projectile-commander-bindings () 1098 | (def-projectile-commander-method ?a 1099 | "Run ack on project." 1100 | (call-interactively 'helm-projectile-ack)) 1101 | 1102 | (def-projectile-commander-method ?A 1103 | "Find ag on project." 1104 | (call-interactively 'helm-projectile-ag)) 1105 | 1106 | (def-projectile-commander-method ?f 1107 | "Find file in project." 1108 | (helm-projectile-find-file)) 1109 | 1110 | (def-projectile-commander-method ?b 1111 | "Switch to project buffer." 1112 | (helm-projectile-switch-to-buffer)) 1113 | 1114 | (def-projectile-commander-method ?d 1115 | "Find directory in project." 1116 | (helm-projectile-find-dir)) 1117 | 1118 | (def-projectile-commander-method ?g 1119 | "Run grep on project." 1120 | (helm-projectile-grep)) 1121 | 1122 | (def-projectile-commander-method ?s 1123 | "Switch project." 1124 | (helm-projectile-switch-project)) 1125 | 1126 | (def-projectile-commander-method ?e 1127 | "Find recently visited file in project." 1128 | (helm-projectile-recentf)) 1129 | 1130 | (def-projectile-commander-method ?V 1131 | "Find dirty projects." 1132 | (helm-projectile-browse-dirty-projects))) 1133 | 1134 | ;;;###autoload 1135 | (defun helm-projectile-toggle (toggle) 1136 | "Toggle Helm version of Projectile commands." 1137 | (if (> toggle 0) 1138 | (progn 1139 | (when (eq projectile-switch-project-action #'projectile-find-file) 1140 | (setq projectile-switch-project-action #'helm-projectile-find-file)) 1141 | (define-key projectile-mode-map [remap projectile-find-other-file] #'helm-projectile-find-other-file) 1142 | (define-key projectile-mode-map [remap projectile-find-file] #'helm-projectile-find-file) 1143 | (define-key projectile-mode-map [remap projectile-find-file-in-known-projects] #'helm-projectile-find-file-in-known-projects) 1144 | (define-key projectile-mode-map [remap projectile-find-file-dwim] #'helm-projectile-find-file-dwim) 1145 | (define-key projectile-mode-map [remap projectile-find-dir] #'helm-projectile-find-dir) 1146 | (define-key projectile-mode-map [remap projectile-switch-project] #'helm-projectile-switch-project) 1147 | (define-key projectile-mode-map [remap projectile-recentf] #'helm-projectile-recentf) 1148 | (define-key projectile-mode-map [remap projectile-switch-to-buffer] #'helm-projectile-switch-to-buffer) 1149 | (define-key projectile-mode-map [remap projectile-grep] #'helm-projectile-grep) 1150 | (define-key projectile-mode-map [remap projectile-ack] #'helm-projectile-ack) 1151 | (define-key projectile-mode-map [remap projectile-ag] #'helm-projectile-ag) 1152 | (define-key projectile-mode-map [remap projectile-ripgrep] #'helm-projectile-rg) 1153 | (define-key projectile-mode-map [remap projectile-browse-dirty-projects] #'helm-projectile-browse-dirty-projects) 1154 | (helm-projectile-commander-bindings)) 1155 | (progn 1156 | (when (eq projectile-switch-project-action #'helm-projectile-find-file) 1157 | (setq projectile-switch-project-action #'projectile-find-file)) 1158 | (define-key projectile-mode-map [remap projectile-find-other-file] nil) 1159 | (define-key projectile-mode-map [remap projectile-find-file] nil) 1160 | (define-key projectile-mode-map [remap projectile-find-file-in-known-projects] nil) 1161 | (define-key projectile-mode-map [remap projectile-find-file-dwim] nil) 1162 | (define-key projectile-mode-map [remap projectile-find-dir] nil) 1163 | (define-key projectile-mode-map [remap projectile-switch-project] nil) 1164 | (define-key projectile-mode-map [remap projectile-recentf] nil) 1165 | (define-key projectile-mode-map [remap projectile-switch-to-buffer] nil) 1166 | (define-key projectile-mode-map [remap projectile-grep] nil) 1167 | (define-key projectile-mode-map [remap projectile-ag] nil) 1168 | (define-key projectile-mode-map [remap projectile-ripgrep] nil) 1169 | (define-key projectile-mode-map [remap projectile-browse-dirty-projects] nil) 1170 | (projectile-commander-bindings)))) 1171 | 1172 | ;;;###autoload 1173 | (defun helm-projectile (&optional arg) 1174 | "Use projectile with Helm instead of ido. 1175 | 1176 | With a prefix ARG invalidates the cache first. 1177 | If invoked outside of a project, displays a list of known projects to jump." 1178 | (interactive "P") 1179 | (if (not (projectile-project-p)) 1180 | (helm-projectile-switch-project arg) 1181 | (projectile-maybe-invalidate-cache arg) 1182 | (let ((helm-ff-transformer-show-only-basename nil)) 1183 | (helm :sources helm-projectile-sources-list 1184 | :buffer "*helm projectile*" 1185 | :truncate-lines helm-projectile-truncate-lines 1186 | :prompt (projectile-prepend-project-name (if (projectile-project-p) 1187 | "pattern: " 1188 | "Switch to project: ")))))) 1189 | 1190 | ;;;###autoload 1191 | (eval-after-load 'projectile 1192 | '(progn 1193 | (define-key projectile-command-map (kbd "h") #'helm-projectile))) 1194 | 1195 | (provide 'helm-projectile) 1196 | 1197 | ;;; helm-projectile.el ends here 1198 | -------------------------------------------------------------------------------- /screenshots/helm-projectile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bbatsov/helm-projectile/041076e35a6663302a91a0fa672f847c7d64bf29/screenshots/helm-projectile.png --------------------------------------------------------------------------------