* find-file-in-project v5.4.6

[[http://stable.melpa.org/#/find-file-in-project][file:http://stable.melpa.org/packages/find-file-in-project]]

Find file/directory and review Diff/Patch/Commit quickly everywhere.

*User Case One:*
Find file/directory quickly in current project. The project root is detected *automatically* if Git/Subversion/Mercurial is used.

*User Case Two:*
Diff/patch files. Target files could be understand any Version Control Software (VCS) or there is no VCS at all. Please check =ffip-diff-*= commands.

*Features:*
- Works perfectly on any OS with minimum setup. Only dependency is =BSD/GNU Find=
- Quick. Tested with 50,000+ files
- You can speed up searching by tweaking =ffip-find-options= and =ffip-prune-patterns=

*Screenshot:*

[[https://raw.githubusercontent.com/technomancy/find-file-in-project/master/ffip-screenshot-nq8.png]]

* Install
Place [[https://raw.githubusercontent.com/technomancy/find-file-in-project/master/find-file-in-project.el][find-file-in-project.el]] under your [[https://www.emacswiki.org/emacs/LoadPath][Load Path]]. Then add =(require 'find-file-in-project)= to your configuration.

It is also possible to use [[http://stable.melpa.org/#/find-file-in-project][melpa]]; however be aware that as of the time of this writing installation using =package.el= is [[https://glyph.twistedmatrix.com/2015/11/editor-malware.html][not recommended]] due to flaws in Emacs's TLS implementation.

[[https://github.com/abo-abo/swiper][Ivy-mode]] is the optional dependency which is installed automatically if you use melpa. If it is not found, ido will be used instead.

Since v3.7, Emacs 24.3 is required.

Users of Debian ≥10 and derivatives can install find-file-in-project with the following command:
=sudo apt install elpa-find-file-in-project=
* Setup
** Windows
Windows setup is as easy as *installing [[http://cygwin.com][Cygwin]] or [[https://msys2.github.io/][MYSYS2]] at default directory of any driver*. =GNU Find= executable is detected automatically.

You can also manually specify the file path,
#+begin_src elisp
(if (eq system-type 'windows-nt)
    (setq ffip-find-executable "c:\\\\cygwin64\\\\bin\\\\find"))
#+end_src

** Linux and OS X
NO setup needed.
* Usage
Project root is *automatically* detected if Git/Mercurial/Subversion is used.

You can override the default root directory by setting =ffip-project-root=,
#+begin_src elisp
(setq ffip-project-root "~/projs/PROJECT_DIR")
#+end_src

Per-project and per-directory setup is easy. Check "Tips" section for details.
** =M-x find-file-in-project-by-selected=
Use the *selected region as keyword to search file*. If no region selected, you may provide the keyword which could contain wildcard.

If keyword contains line number like "hello.txt:32" or "hello.txt:32:", we will move to that line in opened file.

If parameter is passed , file will be opened in new window.

If you =(setq ffip-match-path-instead-of-filename t)= before =M-x find-file-in-project-by-selected=, we try to match selected text with any part of full path before displaying candidates. It's a little slower than the original setup. 

It could replace old command =find-file-in-project= (or =ffip=) because it's more efficient. It was tested searching in 50K+ files without any performance issue.
** =M-x find-file-with-similar-name=
Find file with similar name to current opened file.

The regular expression =ffip-strip-file-name-regex= is also used by =find-file-with-similar-name=.
** =M-x find-directory-in-project-by-selected=
Use the *selected region as keyword to find directory*. If no region selected, you need provide the keyword. Keyword could contain wildcard character which passed to Find as value of =-iwholename= option

If parameter is passed , directory will be opened in new window.
** =M-x find-file-in-project=
Starts search immediately. This command is slow if there 10K+ files because it use ONLY Emacs Lisp to filter candidates. You should always use =find-file-in-project-by-selected= unless in small project.
** =M-x ffip-create-project-file=
Create =.dir-locals.el= which "[[http://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html][defines the same set of local variables to all the files in a certain directory and its subdirectory]]".

You can use it to setup variables like =ffip-project-root=.

This command *respects* existing =.dir-locals.el=. So it will *merge* new setup into existing content.

See [[http://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html][Emacs manual]] for technical details.
** =M-x find-file-in-current-directory=
Like =M-x find-file-in-project= but find only in current directory.
** =M-x find-file-in-current-directory-by-selected=
Like =M-x find-file-in-project-by-selected= but find only in current directory.
** =M-x ffip-show-diff=
Execute selected backend from =ffip-diff-backends=.

The output of backend execution is in [[http://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html][Unified Diff Format]] and is inserted into  =*ffip-diff*= buffer where you can press =o=, =C-c C-c=, =ENTER= , =M-x ffip-diff-find-file=  to open the corresponding file.

=ffip-diff-find-file-before-hook= is called before =M-x ffip-diff-find-file=.

For example, you can =M-x ffip-show-diff= to view the git commit and open corresponding file.

If you use [[https://www.mercurial-scm.org/][Mercurial]], =M-x 5 ffip-show-diff=.

Please press =C-h v ffip-diff-backends= to view the available back-ends.

Other key bindings available in =*ffip-diff*= buffer,
| key binding | command        |
|-------------+----------------|
| p           | diff-hunk-prev |
| n           | diff-hunk-next |
| P           | diff-file-prev |
| N           | diff-file-next |

Insert below code into ~/.emacs if you use evil-mode,
#+begin_src elisp
(defun ffip-diff-mode-hook-setup ()
    (evil-local-set-key 'normal "p" 'diff-hunk-prev)
    (evil-local-set-key 'normal "n" 'diff-hunk-next)
    (evil-local-set-key 'normal "P" 'diff-file-prev)
    (evil-local-set-key 'normal "N" 'diff-file-next)
    (evil-local-set-key 'normal (kbd "RET") 'ffip-diff-find-file)
    (evil-local-set-key 'normal "o" 'ffip-diff-find-file))
(add-hook 'ffip-diff-mode-hook 'ffip-diff-mode-hook-setup)
#+end_src

You can customize the =ffip-diff-backends=,
#+begin_src elisp
(setq ffip-diff-backends
      '(ffip-diff-backend-git-show-commit
        "cd $(git rev-parse --show-toplevel) && git diff"
        "cd $(git rev-parse --show-toplevel) && git diff --cached"
        ffip-diff-backend-hg-show-commit
        ("Diff from `kill-ring'" . (car kill-ring))
        "cd $(hg root) && hg diff"
        "svn diff"))
#+end_src
** =M-x ffip-save-ivy-last= and =M-x ffip-ivy-resume=
These two commands requires [[https://github.com/abo-abo/swiper][ivy-mode]].

=ffip-save-ivy-last= saves the most recent search result.

=ffip-ivy-resume= re-use the search result *saved* by =ffip-save-ivy-last=.

You can always use =ivy-resume= to re-use the most recent search result.
** =M-x find-relative-path=
Find file/directory and copy its relative path into `kill-ring'.

File's path is copied by default. =C-u M-x find-relative-path= copy directory's path.

You can set =ffip-find-relative-path-callback= to format the string before copying.
#+begin_src elisp
;; (setq ffip-find-relative-path-callback 'ffip-copy-reactjs-import)
(setq ffip-find-relative-path-callback 'ffip-copy-org-file-link)
#+end_src
** =M-x ffip-diff-apply-hunk=
Similar to =diff-apply-hunk=, it applies current hunk in =diff-mode= to the target file (please note =ffip-diff-mode= inherits from =diff-mode=).

The target file could be located by searching =(ffip-project-root)=. You can also apply extra operation on the file in =ffip-diff-apply-hunk-hook= before hunk applying actually happens.

For example, for files under [[https://www.perforce.com/][Perforce]] control,
#+begin_src elisp
(defun p4-edit-file-and-make-buffer-writable(file)
  "p4 edit FILE and make corresponding buffer writable."
  (shell-command (format "p4 edit %s" file))
  ;; make sure the buffer is readable
  (let* ((buf (get-file-buffer file)))
    (if buf
        (with-current-buffer buf
          ;; turn off read-only since we've already `p4 edit'
          (read-only-mode -1)))))
(defun ffip-diff-apply-hunk-hook-setup (file)
  (unless (featurep 'init-perforce) (require 'init-perforce))
  (if (string-match-p "/myproject/" file)
      (p4-edit-file-and-make-buffer-writable file)))
(add-hook 'ffip-diff-apply-hunk-hook 'ffip-diff-apply-hunk-hook-setup)
#+end_src
** =ffip-split-window-horizontally= and =ffip-split-window-vertically=
Find&Open file in split window.
* Tips
All tips are OPTIONAL. find-file-in-project works out of box in 99% cases.
** Use ido-mode instead of ivy
#+begin_src elisp
(ido-mode 1)
(setq ffip-prefer-ido-mode t)
#+end_src
** APIs
- "ffip-get-project-root-directory" return the full path of current project
** Per-project setup using Emacs lisp
Here is complete setup you could insert into =~/.emacs.d/init.el=,
#+begin_src elisp
;; if the full path of current file is under SUBPROJECT1 or SUBPROJECT2
;; OR if I'm reading my personal issue track document,
(defun my-setup-develop-environment ()
  (interactive)
  (when (ffip-current-full-filename-match-pattern-p "\\(PROJECT_DIR\\|issue-track.org\\)")
    ;; Though PROJECT_DIR is team's project, I care only its sub-directory "subproj1""
    (setq-local ffip-project-root "~/projs/PROJECT_DIR/subproj1")
    ;; well, I'm not interested in concatenated BIG js file or file in dist/
    (setq-local ffip-find-options "-not -size +64k -not -iwholename '*/dist/*'")
    ;; for this project, I'm only interested certain types of files
    (setq-local ffip-patterns '("*.html" "*.js" "*.css" "*.java" "*.xml" "*.js"))
    ;; maybe you want to search files in `bin' directory?
    (setq-local ffip-prune-patterns (delete "*/bin/*" ffip-prune-patterns))
    ;; exclude `dist/' directory
    (add-to-list 'ffip-prune-patterns "*/dist/*"))
  ;; insert more WHEN statements below this line for other projects
  )
;; most major modes inherit from prog-mode, so below line is enough
(add-hook 'prog-mode-hook 'my-setup-develop-environment)
#+end_src
** Per-directory setup using =.dir-locals.el=
All variables may be overridden on a per-directory basis in your =.dir-locals.el=. See (info "(Emacs) Directory Variables") for details.

You can place =.dir-locals.el= into your project root directory.

A sample =.dir-locals.el=,
#+begin_src elisp
((nil . ((ffip-project-root . "~/projs/PROJECT_DIR")
         ;; ingore files bigger than 64k and directory "dist/"
         (ffip-find-options . "-not -size +64k -not -iwholename '*/dist/*'")
         ;; only search files with following extensions
         (ffip-patterns . ("*.html" "*.js" "*.css" "*.java" "*.xml" "*.js"))
         (eval . (progn
                   (require 'find-file-in-project)
                   ;; ingore directory ".tox/"
                   (setq ffip-prune-patterns `("*/.tox/*" ,@ffip-prune-patterns))
                   ;; Do NOT ignore directory "bin/"
                   (setq ffip-prune-patterns `(delete "*/bin/*" ,@ffip-prune-patterns))))
         )))
#+end_src

As we mentioned, =ffip-create-project-file= could create a minimum =.dir-locals.el= for you.

BTW, please use either per-directory setup or per-project setup, *NOT* both.
** Specify root directory on Windows
#+begin_src elisp
(if (eq system-type 'windows-nt)
    ;; Native Windows
    (setq ffip-project-root "C:/Users/myname/projs/myproj1")
  ;; Cygwin
  (setq ffip-project-root "~/projs/myprojs1"))
#+end_src
** Search multiple file name patterns
The variable =ffip-filename-rules= create some extra file names for
search when calling =find-file-in-project-by-selected=. For example,
When file basename =helloWorld= provided, =HelloWorld=, =hello-world=
are added as the file name search patterns.

=C-h v ffip-filename-rules= to see its default value.

As other variables, it could be customized per project,
#+begin_src elisp
(setq-local ffip-filename-rules
            '(;; first, search by the original file name
              ffip-filename-identity
              ;; second, apply either below rule
              (ffip-filename-dashes-to-camelcase ffip-filename-camelcase-to-dashes)))
#+end_src
** More keybinding tips
=C-h i g (ivy) Enter= for more key-binding tips.
** Search and grep files under Git control
Install [[https://github.com/abo-abo/swiper][counsel]].

Use =cousel-git= to find file and =counsel-git-grep= to grep. It's developed by the author of Ivy.
* Development
This program *only* uses =ivy-read= from ivy-mode.

DO NOT use other APIs from =ivy-mode=. The less APIs used, the more stable this package will be.

Run =tests/test.sh= before =git commit=.
* Bug Report
Check [[https://github.com/technomancy/find-file-in-project]].
* License
find-file-in-project is free software: you can redistribute it and/or modify it under the terms of the [[https://raw.githubusercontent.com/technomancy/find-file-in-project/master/LICENSE][GNU General Public License]] as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

find-file-in-project 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 [[https://raw.githubusercontent.com/technomancy/find-file-in-project/master/LICENSE][GNU General Public License]] for more details.
