redguardtoo / find-file-in-project

Quick access to project files in Emacs
GNU General Public License v3.0
428 stars 57 forks source link

[[https://github.com/redguardtoo/find-file-in-project/actions/workflows/test.yml][https://github.com/redguardtoo/find-file-in-project/actions/workflows/test.yml/badge.svg]] [[http://melpa.org/#/find-file-in-project][file:http://melpa.org/packages/find-file-in-project-badge.svg]] [[http://stable.melpa.org/#/find-file-in-project][file:http://stable.melpa.org/packages/find-file-in-project-badge.svg]]

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 under any Version Control Software (VCS) or no VCS at all. Please check =ffip-diff-*= commands.

Features:

Screenshot:

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

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.

Since =v3.7=, =Emacs 24.3= is required.

Since =v5.7.5=, =Emacs 24.4= is required.

Since =v6=, =Emacs 25.1= is required.

Users of Debian ≥10 and derivatives can install this program with the following command: =sudo apt install elpa-find-file-in-project=

It only uses builtin api =completing-read=.

Ido setup,

+begin_src elisp

(setq ffip-prefer-ido-mode t)

+end_src

Helm setup,

+begin_src elisp

(helm-mode 1)

+end_src

Ivy setup,

+begin_src elisp

(ivy-mode 1)

+end_src

* 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 executable path,

+begin_src elisp

(when (eq system-type 'windows-nt) (setq ffip-find-executable "c:\\cygwin64\\bin\\find"))

+end_src

** Linux and OS X NO setup needed.

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. =find-file-in-project-at-point= Guess the file path at point and try to find file. The path could contain environment variables. =find-file-in-project-by-selected= Use the selected region as keyword to search file. If no region is active, you could 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 =(setq ffip-match-path-instead-of-filename t)= is placed 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 could replace old command =find-file-in-project= (or =ffip=) because it's faster. It was tested searching in 50K+ files without any performance issue. ** =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=. ** =find-directory-in-project-by-selected= Use the selected region as keyword to find directory. If no region is active, you could 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. =ffip-fix-file-path-at-point= It replaces file path at point with correct relative/absolute path. File path could contain environment variables. =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= in big project.. ** =ffip-find-files-resume= File/directory searching actions are automatically stored into =ffip-find-files-history=.

Use =ffip-find-files-resume= to replay any previous action.

The maximum number of items of the history is set in =ffip-find-files-history-max-items=. ** =ffip-lisp-find-file-in-project= By default it finds file in project. f its parameter is not nil, it find directory instead.

It's written in pure Lisp and does not use any third party command line program. So it works in all environments. ** =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 setup variables like =ffip-project-root= in this file.

The original setup in =.dir-locals.el= is respected. This command will merge new setup with old content.

See [[http://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html][Emacs manual]] for technical details. =find-file-in-current-directory= Like =find-file-in-project= but find file in current directory. =find-file-in-current-directory-by-selected= Like =find-file-in-project-by-selected= but find file in current directory. ** =ffip-show-diff= Execute 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 in =ffip-diff-find-file=. Two file names are passed to it as parameters. One name is returned by the hook as the file searching keyword.

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

, =M-x 5 ffip-show-diff= executes 5th backend from =ffip-diff-backends=.

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

Other key bindings defined 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 "K" 'diff-hunk-prev) (evil-local-set-key 'normal "J" '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

Please note some backends assume that the git cli program is added into environment variable PATH. ** =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

** =ffip-diff-apply-hunk= Similar to =diff-apply-hunk=, it applies current hunk on the target file (please note =ffip-diff-mode= inherits from =diff-mode=).

The target file could be found by searching =(ffip-project-root)=. You can also apply extra operation on the file in =ffip-diff-apply-hunk-hook= before hunk applying 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-diff-filter-hunks-by-file-name= It can filter hunks by their file names.

For example, user input pattern "regex !exclude1 exclude1" means the hunk's file name does match "regex", but does not match "exclude1" or "exclude2".

Please note in "regex", space represents any string. ** =ffip-insert-file= Insert file content into current buffer.

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") ;; ignore files bigger than 64k and directory "dist/" when searching (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) ;; ignore directory ".tox/" when searching (setq ffip-prune-patterns ("*/.tox" ,@ffip-prune-patterns)) ;; Do NOT ignore directory "bin/" when searching (setq ffip-prune-patterns(delete "*/bin" ,@ffip-prune-patterns)))) )))

+end_src

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

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 and grep files under Git control Install [[https://github.com/abo-abo/swiper][counsel]].

Use =counsel-git= to find file and =counsel-git-grep= to grep.

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 [[file:LICENSE][GNU General Public License]] for more details.