You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

455 lines
20 KiB

;;; helm-projectile.el --- Helm integration for Projectile
;; Copyright (C) 2011-2013 Bozhidar Batsov
;; Author: Bozhidar Batsov
;; URL: https://github.com/bbatsov/projectile
;; Created: 2011-31-07
;; Keywords: project, convenience
;; Version: 20141017.246
;; X-Original-Version: 0.11.0
;; Package-Requires: ((helm "1.4.0") (projectile "0.11.0") (cl-lib "0.3"))
;; This file is NOT part of GNU Emacs.
;;; License:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This library provides easy project management and navigation. The
;; concept of a project is pretty basic - just a folder containing
;; special file. Currently git, mercurial and bazaar repos are
;; considered projects by default. If you want to mark a folder
;; manually as a project just create an empty .projectile file in
;; it. See the README for more details.
;;
;;; Code:
(require 'projectile)
(require 'helm-config)
(require 'helm-locate)
(require 'helm-buffers)
(require 'helm-files)
(require 'cl-lib)
(declare-function eshell "eshell")
(defgroup helm-projectile nil
"Helm support for projectile."
:prefix "helm-projectile-"
:group 'projectile
:link `(url-link :tag "helm-projectile homepage" "https://github.com/bbatsov/projectile"))
(defvar helm-projectile-current-project-root)
(defun helm-projectile-coerce-file (candidate)
(with-current-buffer (helm-candidate-buffer)
(expand-file-name candidate helm-projectile-current-project-root)))
(defmacro helm-projectile-define-key (keymap key def &rest bindings)
"In KEYMAP, define key sequence KEY1 as DEF1, KEY2 as DEF2 ..."
(declare (indent defun))
(let ((ret '(progn)))
(while key
(add-to-list
'ret
`(define-key ,keymap ,key
(lambda ()
(interactive)
(helm-quit-and-execute-action ,def)))
'append)
(setq key (pop bindings)
def (pop bindings)))
ret))
(defun helm-projectile-vc (dir)
"A Helm action for jumping to project root using `vc-dir' or Magit.
DIR is a directory to be switched"
(let ((projectile-require-project-root nil))
(cond
((and (eq (projectile-project-vcs dir) 'git) (fboundp 'magit-status))
(magit-status dir))
(t (vc-dir dir)))))
(defun helm-projectile-compile-project (dir)
"A Helm action for compile a project.
DIR is the project root."
(let ((helm--reading-passwd-or-string t)
(default-directory dir))
(projectile-compile-project helm-current-prefix-arg dir)))
(defun helm-projectile-remove-known-project (_ignore)
"Delete selected projects.
_IGNORE means the argument does not matter.
It is there because Helm requires it."
(let* ((projects (helm-marked-candidates :with-wildcard t))
(len (length projects)))
(with-helm-display-marked-candidates
helm-marked-buffer-name
projects
(if (not (y-or-n-p (format "Delete *%s Projects(s)" len)))
(message "(No deletion performed)")
(progn
(mapc (lambda (p)
(delete p projectile-known-projects))
projects)
(projectile-save-known-projects))
(message "%s Projects(s) deleted" len)))))
(defvar helm-source-projectile-projects
`((name . "Projectile projects")
(candidates . (lambda ()
(if (projectile-project-p)
(cons (abbreviate-file-name (projectile-project-root))
(projectile-relevant-known-projects))
projectile-known-projects)))
(keymap . ,(let ((map (make-sparse-keymap)))
(set-keymap-parent map helm-map)
(helm-projectile-define-key map
(kbd "C-d") 'dired
(kbd "M-g") 'helm-projectile-vc
(kbd "M-e") 'helm-projectile-switch-to-eshell
(kbd "C-s") 'helm-find-files-grep
(kbd "C-c") 'helm-projectile-compile-project
(kbd "M-D") 'helm-projectile-remove-known-project)
map))
(action . (("Switch to project" .
(lambda (project)
(let ((projectile-completion-system 'helm))
(projectile-switch-project-by-name project))))
("Open Dired in project's directory `C-d'" . dired)
("Open project root in vc-dir or magit `M-g'" .
helm-projectile-vc)
("Switch to Eshell `M-e'" . helm-projectile-switch-to-eshell)
("Grep in projects `C-s'. With C-u, recurse" . helm-find-files-grep)
("Compile project `C-c'. With C-u, new compile command"
. helm-projectile-compile-project)
("Remove project(s) `M-D'" . helm-projectile-remove-known-project))))
"Helm source for known projectile projects.")
(defun helm-projectile-init-buffer-with-files (project-root files)
(with-current-buffer (helm-candidate-buffer project-root)
(set (make-local-variable 'helm-projectile-current-project-root)
project-root)
(dolist (file files)
(insert (concat file "\n")))))
(defvar helm-projectile-find-file-map
(let ((map (copy-keymap helm-find-files-map)))
(define-key map (kbd "<left>") 'helm-previous-source)
(define-key map (kbd "<right>") 'helm-next-source)
(helm-projectile-define-key map
(kbd "M-e") 'helm-projectile-switch-to-eshell
(kbd "M-.") 'helm-projectile-ff-etags-select-action
(kbd "M-!") 'helm-projectile-find-files-eshell-command-on-file-action)
map))
(define-key helm-etags-map (kbd "C-c p f") (lambda ()
(interactive)
(helm-run-after-quit 'helm-projectile-find-file nil)))
(defun helm-projectile-find-files-eshell-command-on-file-action (_candidate)
(interactive)
(let* ((helm-ff-default-directory (file-name-directory _candidate)))
(helm-find-files-eshell-command-on-file _candidate)))
(defun helm-projectile-ff-etags-select-action (_candidate)
(interactive)
(let* ((helm-ff-default-directory (file-name-directory _candidate)))
(helm-ff-etags-select _candidate)))
(defun helm-projectile-switch-to-eshell (dir)
(interactive)
(let* ((helm-ff-default-directory (file-name-directory dir)))
(helm-ff-switch-to-eshell dir)))
(defvar helm-projectile-file-actions
(helm-make-actions
"Find File" 'helm-find-file-or-marked
"Find file in Dired" 'helm-point-file-in-dired
(lambda () (and (locate-library "elscreen") "Find file in Elscreen"))
'helm-elscreen-find-file
"View file" 'view-file
"Checksum File" 'helm-ff-checksum
"Query replace on marked" 'helm-ff-query-replace-on-marked
"Serial rename files" 'helm-ff-serial-rename
"Serial rename by symlinking files" 'helm-ff-serial-rename-by-symlink
"Serial rename by copying files" 'helm-ff-serial-rename-by-copying
"Open file with default tool" 'helm-open-file-with-default-tool
"Find file in hex dump" 'hexl-find-file
"Insert as org link `C-c @'" 'helm-files-insert-as-org-link
"Open file externally `C-c C-x, C-u to choose'" 'helm-open-file-externally
"Grep File(s) `C-s, C-u Recurse'" 'helm-find-files-grep
"Zgrep File(s) `M-g z, C-u Recurse'" 'helm-ff-zgrep
"Switch to Eshell `M-e'" 'helm-projectile-switch-to-eshell
"Etags `M-., C-u reload tag file'" 'helm-projectile-ff-etags-select-action
"Eshell command on file(s) `M-!, C-u take all marked as arguments.'" 'helm-projectile-find-files-eshell-command-on-file-action
"Find file as root `C-c r'" 'helm-find-file-as-root
"Ediff File `C-='" 'helm-find-files-ediff-files
"Ediff Merge File `C-c ='" 'helm-find-files-ediff-merge-files
"Delete File(s) `M-D'" 'helm-delete-marked-files
"Copy file(s) `M-C, C-u to follow'" 'helm-find-files-copy
"Rename file(s) `M-R, C-u to follow'" 'helm-find-files-rename
"Symlink files(s) `M-S, C-u to follow'" 'helm-find-files-symlink
"Relsymlink file(s) `C-u to follow'" 'helm-find-files-relsymlink
"Hardlink file(s) `M-H, C-u to follow'" 'helm-find-files-hardlink
"Find file other window `C-c o'" 'find-file-other-window
"Switch to history `M-p'" 'helm-find-files-switch-to-hist
"Find file other frame `C-c C-o'" 'find-file-other-frame
"Print File `C-c p, C-u to refresh'" 'helm-ff-print
"Locate `C-x C-f, C-u to specify locate db'" 'helm-ff-locate)
"Action for files.")
(defvar helm-source-projectile-files-dwim-list
`((name . "Projectile Files")
(init . (lambda ()
(let* ((project-files (projectile-current-project-files))
(files (projectile-select-files project-files)))
(cond
((= (length files) 1)
(find-file (expand-file-name (car files) (projectile-project-root)))
(helm-exit-minibuffer))
((> (length files) 1)
(helm-projectile-init-buffer-with-files (projectile-project-root)
files))
(t
(helm-projectile-init-buffer-with-files (projectile-project-root)
project-files))))))
(coerce . helm-projectile-coerce-file)
(candidates-in-buffer)
(keymap . ,(let ((map (copy-keymap helm-find-files-map)))
(define-key map (kbd "<left>") 'helm-previous-source)
(define-key map (kbd "<right>") 'helm-next-source)
map))
(help-message . helm-find-file-help-message)
(mode-line . helm-ff-mode-line-string)
(type . file)
(action . ,helm-projectile-file-actions))
"Helm source definition for Projectile files")
(defvar helm-source-projectile-files-list
`((name . "Projectile Files")
(init . (lambda ()
(helm-projectile-init-buffer-with-files (projectile-project-root)
(projectile-current-project-files))))
(coerce . helm-projectile-coerce-file)
(candidates-in-buffer)
(keymap . ,helm-projectile-find-file-map)
(help-message . helm-find-file-help-message)
(mode-line . helm-ff-mode-line-string)
(type . file)
(action . ,helm-projectile-file-actions))
"Helm source definition for Projectile files")
(defun helm-projectile-dired-find-dir (dir)
"Jump to a selected directory DIR from helm-projectile."
(dired (expand-file-name dir (projectile-project-root)))
(run-hooks 'projectile-find-dir-hook))
(defun helm-projectile-dired-find-dir-other-window (dir)
"Jump to a selected directory DIR from helm-projectile."
(dired-other-window (expand-file-name dir (projectile-project-root)))
(run-hooks 'projectile-find-dir-hook))
(defvar helm-source-projectile-directories-list
`((name . "Projectile Directories")
(candidates . (lambda ()
(if projectile-find-dir-includes-top-level
(append '("./") (projectile-current-project-dirs))
(projectile-current-project-dirs))))
(keymap . ,(let ((map (make-sparse-keymap)))
(set-keymap-parent map helm-map)
(helm-projectile-define-key map
(kbd "C-c o") 'helm-projectile-dired-find-dir-other-window
(kbd "M-e") 'helm-projectile-switch-to-eshell
(kbd "C-s") 'helm-find-files-grep)
map))
(action . (("Open Dired" . helm-projectile-dired-find-dir)
("Open Dired in other window`C-c o'" . helm-projectile-dired-find-dir)
("Switch to Eshell `M-e'" . helm-projectile-switch-to-eshell)
("Grep in projects `C-s C-u Recurse'" . helm-find-files-grep))))
"Helm source for listing project directories")
(defvar helm-source-projectile-buffers-list
`((name . "Projectile Buffers")
(init . (lambda ()
;; Issue #51 Create the list before `helm-buffer' creation.
(setq helm-projectile-buffers-list-cache (projectile-project-buffer-names))
(let ((result (cl-loop for b in helm-projectile-buffers-list-cache
maximize (length b) into len-buf
maximize (length (with-current-buffer b
(symbol-name major-mode)))
into len-mode
finally return (cons len-buf len-mode))))
(unless helm-buffer-max-length
(setq helm-buffer-max-length (car result)))
(unless helm-buffer-max-len-mode
;; If a new buffer is longer that this value
;; this value will be updated
(setq helm-buffer-max-len-mode (cdr result))))))
(candidates . helm-projectile-buffers-list-cache)
(type . buffer)
(match helm-buffers-list--match-fn)
(persistent-action . helm-buffers-list-persistent-action)
(keymap . ,helm-buffer-map)
(volatile)
(no-delay-on-input)
(mode-line . helm-buffer-mode-line-string)
(persistent-help
. "Show this buffer / C-u \\[helm-execute-persistent-action]: Kill this buffer")))
(defvar helm-source-projectile-recentf-list
`((name . "Projectile Recent Files")
;; Needed for filenames with capitals letters.
(init . (lambda ()
(helm-projectile-init-buffer-with-files (projectile-project-root)
(projectile-recentf-files))))
(coerce . helm-projectile-coerce-file)
(candidates-in-buffer)
(keymap . ,helm-generic-files-map)
(help-message . helm-find-file-help-message)
(mode-line . helm-ff-mode-line-string)
(type . file)
(action . ,(cdr (helm-get-actions-from-type
helm-source-locate))))
"Helm source definition.")
(defcustom helm-projectile-sources-list
'(helm-source-projectile-projects
helm-source-projectile-buffers-list
helm-source-projectile-recentf-list
helm-source-projectile-files-list
helm-source-projectile-directories-list
)
"Default sources for `helm-projectile'."
:group 'helm-projectile)
(defmacro helm-projectile-command (command source prompt)
"Template for generic helm-projectile commands.
COMMAND is a command name to be appended with \"helm-projectile\" prefix.
SOURCE is a Helm source that should be Projectile specific.
PROMPT is a string for displaying as a prompt."
`(defun ,(intern (concat "helm-projectile-" command)) (&optional arg)
"Use projectile with Helm for finding files in project
With a prefix ARG invalidates the cache first."
(interactive "P")
(if (projectile-project-p)
(projectile-maybe-invalidate-cache arg))
(let ((helm-ff-transformer-show-only-basename nil))
(helm :sources ,source
:buffer "*helm projectile*"
:prompt (projectile-prepend-project-name ,prompt)))))
(helm-projectile-command "switch-project" 'helm-source-projectile-projects "Switch to project: ")
(helm-projectile-command "find-file" 'helm-source-projectile-files-list "Find file: ")
(helm-projectile-command "find-file-dwim" 'helm-source-projectile-files-dwim-list "Find file: ")
(helm-projectile-command "find-dir" 'helm-source-projectile-directories-list "Find dir: ")
(helm-projectile-command "recentf" 'helm-source-projectile-recentf-list "Recently visited file: ")
(helm-projectile-command "switch-to-buffer" 'helm-source-projectile-buffers-list "Switch to buffer: ")
(defun helm-projectile-find-other-file (&optional flex-matching)
"Switch between files with the same name but different extensions using Helm.
With FLEX-MATCHING, match any file that contains the base name of current file.
Other file extensions can be customized with the variable `projectile-other-file-alist'."
(interactive "P")
(-if-let (other-files (projectile-get-other-files (buffer-file-name)
(projectile-current-project-files)
flex-matching))
(if (= (length other-files) 1)
(find-file (expand-file-name (car other-files) (projectile-project-root)))
(progn
(let* ((helm-ff-transformer-show-only-basename nil))
(helm :sources `((name . "Projectile Other Files")
(init . (lambda ()
(helm-projectile-init-buffer-with-files (projectile-project-root)
other-files)))
(coerce . helm-projectile-coerce-file)
(candidates-in-buffer)
(keymap . ,(let ((map (copy-keymap helm-find-files-map)))
(define-key map (kbd "<left>") 'helm-previous-source)
(define-key map (kbd "<right>") 'helm-next-source)
map))
(help-message . helm-find-file-help-message)
(mode-line . helm-ff-mode-line-string)
(type . file)
(action . ,helm-projectile-file-actions))
:buffer "*helm projectile*"
:prompt (projectile-prepend-project-name "Find other file: ")))))
(error "No other file found")))
(defun helm-projectile-on ()
"Turn on helm-projectile key bindings."
(interactive)
(message "Turn on helm-projectile key bindings")
(helm-projectile-toggle 1))
(defun helm-projectile-off ()
"Turn off helm-projectile key bindings."
(interactive)
(message "Turn off helm-projectile key bindings")
(helm-projectile-toggle -1))
(defun helm-projectile-toggle (toggle)
"Toggle Helm version of Projectile commands."
(if (> toggle 0)
(progn
(define-key projectile-command-map (kbd "a") 'helm-projectile-find-other-file)
(define-key projectile-command-map (kbd "f") 'helm-projectile-find-file)
(define-key projectile-command-map (kbd "g") 'helm-projectile-find-file-dwim)
(define-key projectile-command-map (kbd "d") 'helm-projectile-find-dir)
(define-key projectile-command-map (kbd "p") 'helm-projectile-switch-project)
(define-key projectile-command-map (kbd "e") 'helm-projectile-recentf)
(define-key projectile-command-map (kbd "b") 'helm-projectile-switch-to-buffer))
(progn
(define-key projectile-command-map (kbd "a") 'projectile-find-other-file)
(define-key projectile-command-map (kbd "f") 'projectile-find-file)
(define-key projectile-command-map (kbd "g") 'helm-projectile-find-file-dwim)
(define-key projectile-command-map (kbd "d") 'projectile-find-dir)
(define-key projectile-command-map (kbd "p") 'projectile-switch-project)
(define-key projectile-command-map (kbd "e") 'projectile-recentf)
(define-key projectile-command-map (kbd "b") 'projectile-switch-to-buffer))))
;;;###autoload
(defun helm-projectile (&optional arg)
"Use projectile with Helm instead of ido.
With a prefix ARG invalidates the cache first.
If invoked outside of a project, displays a list of known projects to jump."
(interactive "P")
(if (projectile-project-p)
(projectile-maybe-invalidate-cache arg))
(let ((helm-ff-transformer-show-only-basename nil)
(src (if (projectile-project-p)
helm-projectile-sources-list
helm-source-projectile-projects)))
(helm :sources src
:buffer "*helm projectile*"
:prompt (projectile-prepend-project-name (if (projectile-project-p)
"pattern: "
"Switch to project: ")))))
;;;###autoload
(eval-after-load 'projectile
'(progn
(define-key projectile-command-map (kbd "h") 'helm-projectile)))
(provide 'helm-projectile)
;;; helm-projectile.el ends here