|
|
- ;;; find-file-in-project.el --- Find file/directory and review Diff/Patch/Commit efficiently everywhere
-
- ;; Copyright (C) 2006-2009, 2011-2012, 2015-2018
- ;; Phil Hagelberg, Doug Alcorn, Will Farrington, Chen Bin
- ;;
- ;; Version: 5.7.7
- ;; Package-Version: 20190720.313
- ;; Author: Phil Hagelberg, Doug Alcorn, and Will Farrington
- ;; Maintainer: Chen Bin <chenbin.sh@gmail.com>
- ;; URL: https://github.com/technomancy/find-file-in-project
- ;; Package-Requires: ((ivy "0.10.0") (emacs "24.4"))
- ;; Created: 2008-03-18
- ;; Keywords: project, convenience
- ;; EmacsWiki: FindFileInProject
-
- ;; 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. If not, see <http://www.gnu.org/licenses/>.
-
- ;;; Commentary:
-
- ;; This program provides methods to find file in project.
- ;; - Only dependency is BSD/GNU find
- ;; - Works on Windows with minimum setup
- ;; - Works on Tramp Mode (https://www.emacswiki.org/emacs/TrampMode)
- ;; - fd (faster alternative of find, see https://github.com/sharkdp/fd) is supported
- ;;
- ;; Usage,
- ;; - You can insert "(setq ffip-use-rust-fd t)" into ".emacs" to use fd (alternative of find)
- ;; - `find-file-in-project-at-point' guess the file path at point and
- ;; find file
- ;; - `find-file-in-project-by-selected' uses the selected region
- ;; as the keyword to search file. You can provide the keyword
- ;; if no region is selected.
- ;; - `find-directory-in-project-by-selected' uses the select region
- ;; to find directory. You can provide the keyword if no region
- ;; is selected.
- ;; - `find-file-in-project' will start search file immediately
- ;; - `ffip-create-project-file' creates ".dir-locals.el"
- ;;
- ;; A project is found by searching up the directory tree until a file
- ;; is found that matches `ffip-project-file'.
- ;; You can set `ffip-project-root-function' to provide an alternate
- ;; function to search for the project root. By default, it looks only
- ;; for files whose names match `ffip-patterns',
-
- ;; If you have so many files that it becomes unwieldy, you can set
- ;; `ffip-find-options' to a string which will be passed to the `find'
- ;; invocation in order to exclude irrelevant subdirectories/files.
- ;; For instance, in a Ruby on Rails project, you are interested in all
- ;; .rb files that don't exist in the "vendor" directory. In that case
- ;; you could set `ffip-find-options' to "-not -regex \".*vendor.*\"".
-
- ;; `ffip-insert-file' insert file content into current buffer.
-
- ;; `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'.
- ;;
- ;; all these variables may be overridden on a per-directory basis in
- ;; your ".dir-locals.el". See (info "(Emacs) Directory Variables") for
- ;; details.
- ;;
- ;; Sample ".dir-locals.el",
- ;;
- ;; ((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))
- ;; ignore BMP image file
- ;; (setq ffip-ignore-filenames `("*.bmp" ,@ffip-ignore-filenames))
- ;; ;; Do NOT ignore directory "bin/" when searching
- ;; (setq ffip-prune-patterns `(delete "*/bin" ,@ffip-prune-patterns))))
- ;; )))
- ;;
- ;; To find in current directory, use `find-file-in-current-directory'
- ;; and `find-file-in-current-directory-by-selected'.
- ;;
- ;; `ffip-split-window-horizontally' and `ffip-split-window-vertically' find&open file
- ;; in split window.
-
- ;; `ffip-show-diff' execute the backend from `ffip-diff-backends'.
- ;; The output is in Unified Diff Format and inserted into *ffip-diff* buffer.
- ;; Press "o" or "C-c C-c" or "ENTER" or `M-x ffip-diff-find-file' in the
- ;; buffer to open corresponding file.
- ;;
- ;; `ffip-diff-find-file-before-hook' is called before `ffip-diff-find-file'.
- ;;
- ;; `ffip-diff-apply-hunk' applies current hunk in `diff-mode' (please note
- ;; `ffip-diff-mode' inherits from `diff-mode') to the target.
- ;; file. The target file could be located by searching `recentf-list'.
- ;; Except this extra feature, `ffip-diff-apply-hunk' is same as `diff-apply-hunk'.
- ;; So `diff-apply-hunk' can be replaced by `ffip-diff-apply-hunk'.
-
- ;; If you use `evil-mode', insert below code into ~/.emacs,
- ;; (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)
-
- ;; `find-relative-path' find file/directory and copy its relative path
- ;; into `kill-ring'. You can customize `ffip-find-relative-path-callback'
- ;; to format the relative path,
- ;; (setq ffip-find-relative-path-callback 'ffip-copy-reactjs-import)
- ;; (setq ffip-find-relative-path-callback 'ffip-copy-org-file-link)
-
- ;; `ivy-mode' is used for filter/search UI
- ;; In `ivy-mode', SPACE is translated to regex ".*".
- ;; For example, the search string "dec fun pro" is transformed into
- ;; regular expression "\\(dec\\).*\\(fun\\).*\\(pro\\)"
- ;; `C-h i g (ivy)' for more key-binding tips.
- ;;
- ;; `ffip-save-ivy-last' saves the most recent search result.
- ;; `ffip-ivy-resume' re-use the save result. Both requires `ivy-mode'
- ;; installed.
- ;;
- ;; You can switch to `ido-mode' by `(setq ffip-prefer-ido-mode t)'
-
- ;; BSD/GNU Find can be installed through Cygwin or MYSYS2 on Windows.
- ;; Executable is automatically detected. But you can manually specify
- ;; the executable location by insert below code into ".emacs",
- ;;
- ;; (if (eq system-type 'windows-nt)
- ;; (setq ffip-find-executable "c:\\\\cygwin64\\\\bin\\\\find"))
- ;;
- ;; This program works on Windows/Cygwin/Linux/macOS
- ;;
- ;; See https://github.com/technomancy/find-file-in-project for advanced tips.
-
- ;;; Code:
-
- (require 'diff-mode)
- (require 'windmove)
- (require 'subr-x)
-
- (defvar ffip-use-rust-fd nil "Use use fd instead of find.")
-
- (defvar ffip-rust-fd-respect-ignore-files t
- "Don 't show search results from '.*ignore' files")
-
- (defvar ffip-rust-fd-extra-opts ""
- "rust fd extra options passed to cli.")
-
- (defvar ffip-window-ratio-alist
- '((1 . 1.61803398875)
- (2 . 2)
- (3 . 3)
- (4 . 4)
- (5 . 0.61803398875))
- "Dictionary to look up windows split ratio.
- Used by `ffip-split-window-horizontally' and `ffip-split-window-vertically'.")
-
- (defvar ffip-filename-history nil)
-
- (defvar ffip-strip-file-name-regex
- "\\(\\.mock\\|\\.test\\|\\.mockup\\)"
- "Strip file name to get minimum keyword with this regex.
- It's used by `find-file-with-similar-name'.")
-
- (defvar ffip-split-window-without-asking-for-keyword nil
- "`ffip-split-window-horizontally' or `ffip-split-window-vertically' don't ask keyword.")
-
- (defvar ffip-diff-find-file-before-hook nil
- "Hook before `ffip-diff-find-file' move focus out of *ffip-diff* buffer.")
-
- (defvar ffip-read-file-name-hijacked-p nil
- "Internal flag used by `ffip-diff-apply-hunk'.")
-
- (defvar ffip-diff-apply-hunk-hook nil
- "Hook when `ffip-diff-apply-hunk' find the file to apply hunk.
- The file path is passed to the hook as the first argument.")
-
- (defvar ffip-relative-path-pattern "^\\(\\.\\.*/\\)+"
- "Pattern of relative path.")
-
- (defun ffip-shell-command-to-string (command)
- "Execute shell COMMAND and return its output as a string."
- (with-output-to-string
- (with-current-buffer
- standard-output
- (shell-command command t))))
-
- (defun ffip-nonempty-lines (s)
- "Return non empty lines."
- (split-string s "[\r\n]+" t))
-
- (defun ffip-diff-git-versions ()
- "List all versions of code under Git."
- (let* ((git-cmd (concat "git --no-pager log --date=short --pretty=format:'%h|%ad|%s|%an' "
- buffer-file-name)))
- (nconc (ffip-nonempty-lines (shell-command-to-string "git branch --no-color --all"))
- (ffip-nonempty-lines (shell-command-to-string git-cmd)))))
-
- ;;;###autoload
- (defun ffip-git-diff-current-file ()
- "Run 'git diff version:current-file current-file'."
- (let* ((default-directory (locate-dominating-file default-directory ".git"))
- (line (ivy-read "diff current file:" (ffip-diff-git-versions))))
- (ffip-shell-command-to-string (format "git --no-pager diff %s:%s %s"
- (replace-regexp-in-string "^ *\\*? *" "" (car (split-string line "|" t)))
- (file-relative-name buffer-file-name default-directory)
- buffer-file-name))))
-
- (defun ffip-git-diff-project()
- "Run 'git diff version' in project."
- (let* ((default-directory (locate-dominating-file default-directory ".git"))
- (line (ivy-read "diff current file:" (ffip-diff-git-versions)))
- (version (replace-regexp-in-string "^ *\\*? *" "" (car (split-string line "|" t)))))
- (ffip-shell-command-to-string (format "git --no-pager diff %s" version))))
-
- (defvar ffip-diff-backends
- '(ffip-git-diff-current-file
- ffip-git-diff-project
- ("`git diff HEAD^` in project" . "cd $(git rev-parse --show-toplevel) && git diff HEAD^")
- ("`git diff --cached` in project" . "cd $(git rev-parse --show-toplevel) && git diff --cached")
- ("`git diff` in project" . "cd $(git rev-parse --show-toplevel) && git diff")
- ("`git diff` current file" . (ffip-shell-command-to-string (format "cd $(git rev-parse --show-toplevel) && git diff '%s'"
- (buffer-file-name))))
- ("`git log -p` current file" . (ffip-shell-command-to-string (format "cd $(git rev-parse --show-toplevel) && git --no-pager log --date=short -p '%s'"
- (buffer-file-name))))
- ("`git log -S keyword -p` in project" . (ffip-shell-command-to-string (format "cd $(git rev-parse --show-toplevel) && git --no-pager log --date=short -S'%s' -p"
- (read-string "Git search string:"))))
- ("Diff from `kill-ring'" . (car kill-ring)))
- "The list of back-ends.
- If back-end is string, it's run in `shell-command-to-string'.
- If it's a function or expression, it'll be executed and return a string.
-
- The output is inserted into *ffip-diff* buffer.")
-
- (defvar ffip-find-executable nil "Path of GNU find. If nil we will guess.")
-
- (defvar ffip-project-file '(".svn" ".hg" ".git")
- "The file/directory used to locate project root.
- May be set using .dir-locals.el. Checks each entry if set to a list.")
-
- (defvar ffip-prefer-ido-mode (not (require 'ivy nil t))
- "Use ido instead of ivy to display candidates.")
-
- (defvar ffip-patterns nil
- "List of glob patterns to look for with `find-file-in-project'.")
-
- (defvar ffip-match-path-instead-of-filename nil
- "Match full path instead of file name.")
-
- ;; For "GNU/BSD Find", "*/test/*" matches "./test/" and "./dir/test/"
- ;;
- ;; But for "rust fd", only "test/*" matches "./test/" and "./dir/test/";
- ;; "*/test/*" won't match "./test/" but matches "./dir/test/"
- ;; Maybe it's bug of fd.
- (defvar ffip-prune-patterns
- '(;; VCS
- "*/.git"
- "*/.svn"
- "*/.cvs"
- "*/.tox"
- "*/.bzr"
- "*/.hg"
- "*/bin"
- "*/.DS_Store"
- "*/.sass-cache"
- "*/.npm"
- "*/.tmp"
- "*/.idea"
- "*/node_modules"
- "*/bower_components"
- "*/.gradle"
- "*/.cask")
- "Ignored directories(prune patterns).")
-
- (defvar ffip-ignore-filenames
- '(;; VCS
- ;; project misc
- "*.log"
- ;; Ctags
- "tags"
- "TAGS"
- ;; compressed
- "*.tgz"
- "*.gz"
- "*.xz"
- "*.zip"
- "*.tar"
- "*.rar"
- ;; Global/Cscope
- "GTAGS"
- "GPATH"
- "GRTAGS"
- "cscope.files"
- ;; html/javascript/css
- "*bundle.js"
- "*min.js"
- "*min.css"
- ;; Images
- "*.png"
- "*.jpg"
- "*.jpeg"
- "*.gif"
- "*.bmp"
- "*.tiff"
- "*.ico"
- ;; documents
- "*.doc"
- "*.docx"
- "*.xls"
- "*.ppt"
- "*.pdf"
- "*.odt"
- ;; C/C++
- "*.obj"
- "*.so"
- "*.o"
- "*.a"
- "*.ifso"
- "*.tbd"
- "*.dylib"
- "*.lib"
- "*.d"
- "*.dll"
- "*.exe"
- ;; Java
- ".metadata*"
- "*.class"
- "*.war"
- "*.jar"
- ;; Emacs/Vim
- "*flymake"
- "#*#"
- ".#*"
- "*.swp"
- "*~"
- "*.elc"
- ;; Python
- "*.pyc")
- "Ignore file names. Wildcast is supported.")
-
- (defvar ffip-find-options ""
- "Extra options to pass to `find' when using `find-file-in-project'.
-
- Use this to exclude portions of your project: \"-not -regex \\\".*svn.*\\\"\".")
-
- (defvar ffip-find-pre-path-options ""
- "Extra options to pass to `find' before path name options when using `find-file-in-project'.
-
- As required by `find', `-H', `-L', `-P', `-D' and `-O' must appear before the first path name, `.'.
- For example, use this to follow symbolic links inside your project: \"-L\".")
-
- (defvar ffip-project-root nil
- "If non-nil, overrides the project root directory location.")
-
- (defvar ffip-project-root-function nil
- "If non-nil, this function is called to determine the project root.
-
- This overrides variable `ffip-project-root' when set.")
-
- (defvar ffip-ivy-last-saved nil
- "Backup of `ivy-last'. Requires ivy.")
-
- (defvar ffip-debug nil "Print debug information.")
-
- ;;;###autoload
- (defun ffip-copy-without-change (p)
- "Copy P without change."
- (kill-new p)
- (message "%s => kill-ring" p))
-
- ;;;###autoload
- (defun ffip-copy-reactjs-import(p)
- "Create ReactJS link from P and copy the result."
- (setq p (format "import str from '%s';" p))
- (kill-new p)
- (message "%s => kill-ring" p))
-
- ;;;###autoload
- (defun ffip-copy-org-file-link(p)
- "Create org link from P and copy the result."
- (setq p (format "[[file:%s]]" p))
- (kill-new p)
- (message "%s => kill-ring" p))
-
- ;;;###autoload
- (defvar ffip-find-relative-path-callback 'ffip-copy-without-change
- "The callback after calling `find-relative-path'.")
-
- (defun ffip--some (predicate seq)
- "Return if PREDICATE is of true of any element of SEQ"
- (let* (elem rlt)
- (while (and (setq elem (car seq))
- (not rlt))
- (setq seq (cdr seq))
- (setq rlt (funcall predicate elem)))
- rlt))
-
- ;;;###autoload
- (defun ffip-project-root ()
- "Return the root of the project."
- (let* ((project-root (or ffip-project-root
- (cond
- ((functionp ffip-project-root-function)
- (funcall ffip-project-root-function))
- ((listp ffip-project-file)
- (ffip--some (apply-partially 'locate-dominating-file
- default-directory)
- ffip-project-file))
- (t
- (locate-dominating-file default-directory
- ffip-project-file))))))
- (or (and project-root (file-name-as-directory project-root))
- (progn
- (message "Since NO project was found, use `default-directory' instead.")
- default-directory))))
-
- (defun ffip--read-file-text (file)
- "Read text from FILE."
- (read (decode-coding-string
- (with-temp-buffer
- (set-buffer-multibyte nil)
- (setq buffer-file-coding-system 'binary)
- (insert-file-contents-literally file)
- (buffer-substring-no-properties (point-min) (point-max))) 'utf-8)))
-
- ;;;###autoload
- (defun ffip-save-ivy-last ()
- "Save `ivy-last' into `ffip-ivy-last-saved'. Requires ivy."
- (interactive)
- (if (boundp 'ivy-last)
- (setq ffip-ivy-last-saved ivy-last)
- (message "Sorry. You need install `ivy-mode' first.")))
-
- ;;;###autoload
- (defun ffip-get-project-root-directory ()
- "Get the full path of project root directory."
- (if ffip-project-root (file-name-as-directory ffip-project-root)
- (ffip-project-root)))
-
- ;;;###autoload
- (defun ffip-ivy-resume ()
- "Wrapper of `ivy-resume'. Resume the search saved at `ffip-ivy-last-saved'."
- (interactive)
- (let* ((ivy-last (if ffip-ivy-last-saved ffip-ivy-last-saved ivy-last))
- (default-directory (ffip-get-project-root-directory)))
- (if (fboundp 'ivy-resume)
- (ivy-resume)
- (message "Sorry. You need install `ivy-mode' first."))))
-
- ;;;###autoload
- (defun ffip-filename-identity (keyword)
- "Return identical KEYWORD."
- keyword)
-
- ;;;###autoload
- (defun ffip-filename-camelcase-to-dashes (keyword &optional check-only)
- "Convert KEYWORD from camel cased to dash separated.
- If CHECK-ONLY is true, only do the check."
- (let* (rlt)
- (cond
- (check-only
- (setq rlt (string-match "^[a-z0-9]+[A-Z][A-Za-z0-9]+$" keyword))
- (if ffip-debug (message "ffip-filename-camelcase-to-dashes called. check-only keyword=%s rlt=%s" keyword rlt)))
- (t
- (let* ((case-fold-search nil))
- ;; case sensitive replace
- (setq rlt (downcase (replace-regexp-in-string "\\([a-z]\\)\\([A-Z]\\)" "\\1-\\2" keyword))))
-
- (if (string= rlt (downcase keyword)) (setq rlt nil))
-
- (if (and rlt ffip-debug) (message "ffip-filename-camelcase-to-dashes called. rlt=%s" rlt))))
- rlt))
-
- ;;;###autoload
- (defun ffip-filename-dashes-to-camelcase (keyword &optional check-only)
- "Convert KEYWORD from dash separated to camel cased.
- If CHECK-ONLY is true, only do the check."
- (let* (rlt)
- (cond
- (check-only
- (setq rlt (string-match "^[A-Za-z0-9]+\\(-[A-Za-z0-9]+\\)+$" keyword))
- (if ffip-debug (message "ffip-filename-dashes-to-camelcase called. check-only keyword=%s rlt=%s" keyword rlt)))
- (t
- (setq rlt (mapconcat (lambda (s) (capitalize s)) (split-string keyword "-") ""))
-
- (let ((first-char (substring rlt 0 1)))
- (setq rlt (concat "[" first-char (downcase first-char) "]" (substring rlt 1))))
- (if (and rlt ffip-debug) (message "ffip-filename-dashes-to-camelcase called. rlt=%s" rlt))))
- rlt))
-
- (defun ffip--create-filename-pattern-for-gnufind (keyword)
- "Create search pattern from KEYWORD."
- (let* ((rlt ""))
- (cond
- ((not keyword)
- (setq rlt ""))
- (t
- (setq rlt (concat (if ffip-match-path-instead-of-filename "-iwholename" "-iname")
- " \"*"
- keyword
- "*\"" ))))
- (if ffip-debug (message "ffip--create-filename-pattern-for-gnufind called. rlt=%s" rlt))
- rlt))
-
- (defun ffip--win-executable-find (exe)
- "Find EXE on windows."
- (let* ((drivers '("c" "d" "e" "g" "h" "i" "j" "k"))
- (i 0)
- j
- (dirs '(":\\\\cygwin64\\\\bin\\\\"
- ":\\\\msys64\\\\usr\\\\bin\\\\"))
- rlt)
- (while (and (not rlt)
- (< i (length dirs)))
- (setq j 0)
- (while (and (not rlt)
- (< j (length drivers)))
- (setq rlt (executable-find (concat (nth j drivers) (nth i dirs) exe)))))
- (unless rlt
- ;; nothing found, fall back to exe
- (setq rlt exe))
- rlt))
-
- (defun ffip--executable-find ()
- "Find EXE on all environments."
- (let* ((exe (if ffip-use-rust-fd "fd" "find"))
- rlt)
- (cond
- ((file-remote-p default-directory)
- ;; In tramp mode and local windows, remote nix-like,
- ;; the `ffip-find-executable' with windows path can't be applied.
- ;; Assume remote server has already added EXE into $PATH!
- ;; Thanks for ShuguangSun for the fix
- (setq rlt exe))
- ((setq rlt ffip-find-executable))
- ((eq system-type 'windows-nt)
- ;; in case PATH is not setup properly
- (cond
- (ffip-use-rust-fd
- (setq rlt (concat (getenv "USERPROFILE")
- "\\\\.cargo\\\\bin\\\\"
- exe
- ".exe"))
- (unless (file-exists-p rlt)
- (setq rlt exe)))
- (t
- (setq rlt (ffip--win-executable-find exe)))))
- ((setq rlt (executable-find exe)))
- (t
- ;; well, `executable-find' failed
- (setq rlt exe)))
- rlt))
-
- (defun ffip--join-patterns (patterns)
- "Convert PATTERNS into cli arguments."
- (cond
- ((and ffip-patterns (not ffip-use-rust-fd))
- (format "\\( %s \\)" (mapconcat (lambda (pat) (format "-iwholename \"%s\"" pat))
- patterns " -or ")))
- (t
- ;; rust fd only supports ONE pattern (and it's regular expression)
- ;; which is precious resource to waste here
- "")))
-
- (defun ffip--prune-patterns ()
- "Turn `ffip-prune-patterns' into a string that `find' can use."
- ;; Both fd and find use "glob pattern"
- ;; @see https://en.wikipedia.org/wiki/Glob_%28programming%29
- (cond
- (ffip-use-rust-fd
- ;; fd match relative path
- (mapconcat (lambda (p)
- (format "-E \"%s\"" (replace-regexp-in-string "^\*/" "" p)))
- ffip-prune-patterns " "))
- (t
- ;; find match whole path
- (mapconcat (lambda (p)
- (format "-iwholename \"%s\"" p))
- ffip-prune-patterns " -or "))))
-
- (defun ffip--ignore-file-names ()
- "Turn `ffip-ignore-filenames' into a string that `find' can use."
- ;; @see `ffip-prune-patterns' for fd vs find.
- (cond
- (ffip-use-rust-fd
- (mapconcat (lambda (p)
- (format "-E \"%s\"" p))
- ffip-ignore-filenames " "))
- (t
- (mapconcat (lambda (n) (format "-not -name \"%s\"" n))
- ffip-ignore-filenames " "))))
-
- ;;;###autoload
- (defun ffip-completing-read (prompt collection &optional action)
- "Read a string in minibuffer, with completion.
-
- PROMPT is a string with same format parameters in `ido-completing-read'.
- COLLECTION is a list of strings.
-
- ACTION is a lambda function to call after selecting a result.
-
- This function returns the selected candidate or nil."
- (cond
- ((and action (= 1 (length collection)))
- ;; open file directly
- (funcall action (car collection))
- (car collection))
- ;; If user prefer `ido-mode' or there is no ivy,
- ;; use `ido-completing-read'.
- ((or ffip-prefer-ido-mode (not (fboundp 'ivy-read)))
- ;; friendly UI for ido
- (let* ((list-of-pair (consp (car collection)))
- (ido-collection (if list-of-pair
- (mapcar 'car collection)
- collection))
- (ido-selected (ido-completing-read prompt ido-collection)))
- (if (and ido-selected action)
- (funcall action
- (if list-of-pair
- (cdar (delq nil
- (mapcar (lambda (x)
- (and (string= (car x)
- ido-selected)
- x))
- collection)))
- ido-selected)))
- ido-selected))
- (t
- (ivy-read prompt collection
- :action action))))
-
- (defun ffip-create-shell-command (keyword is-finding-directory)
- "Produce command to search KEYWORD.
- If IS-FINDING-DIRECTORY is t, we look up directory instead of file.
- Rust fd use regular expression.
- BSD/GNU Find use glob pattern."
- (let* (cmd fmt tgt)
- (cond
- (ffip-use-rust-fd
- ;; `-H` => search hidden files
- ;; `-E` => exclude pattern
- ;; `-c` => color
- ;; `-i` => case insensitive
- ;; `-t` => directory (d) or file (f)
- ;; `-p` => match full path
- (setq fmt (concat "%s %s -c never -H -i -t %s %s %s %s"
- (if ffip-rust-fd-respect-ignore-files "" " -I")
- (if ffip-match-path-instead-of-filename " -p" "")
- " "
- ffip-rust-fd-extra-opts
- " %s"))
- ;; fd use regular expression for target pattern (but glob pattern when excluding, sigh)
- (setq tgt (if keyword (format "\".*%s\"" keyword) "")))
- (t
- (setq tgt
- (if is-finding-directory (format "-iwholename \"*%s\"" keyword)
- (ffip--create-filename-pattern-for-gnufind keyword)))
- (setq fmt (concat "%s "
- ffip-find-pre-path-options
- " . \\( %s \\) -prune -o -type %s %s %s %s %s -print"))))
-
- (setq cmd (format fmt
- (ffip--executable-find)
- (ffip--prune-patterns)
- (if is-finding-directory "d" "f")
- (ffip--ignore-file-names)
- ffip-find-options
- (ffip--join-patterns ffip-patterns)
- tgt))
- cmd))
-
- (defun ffip-glob-to-regex (s)
- "Convert glob pattern S into regular expression."
- (setq s (replace-regexp-in-string "\\." "\\\\." s))
- (setq s (replace-regexp-in-string "\*" ".*" s))
- s)
-
- ;;;###autoload
- (defun ffip-project-search (keyword is-finding-directory &optional directory-to-search)
- "Return an alist of all filenames in the project and their path.
-
- Files with duplicate filenames are suffixed with the name of the
- directory they are found in so that they are unique.
-
- If KEYWORD is string, it's the file name or file path to find file.
- If KEYWORD is list, it's the list of file names.
- IF IS-FINDING-DIRECTORY is t, we are searching directories, else files.
- DIRECTORY-TO-SEARCH specify the root directory to search."
- (let* ((root (or directory-to-search
- (ffip-get-project-root-directory)))
- (default-directory (file-name-as-directory root))
- (cmd (ffip-create-shell-command keyword is-finding-directory))
- (collection (split-string (ffip-shell-command-to-string cmd) "[\r\n]+" t)))
-
- (if ffip-debug (message "run command at %s: %s" default-directory cmd))
-
- (when (and ffip-use-rust-fd ffip-patterns)
- ;; filter result with Lisp because fd does NOT support multiple patterns
- (let* ((r (concat "^" (mapconcat 'ffip-glob-to-regex ffip-patterns "\\|") "$")))
- (setq collection (delq nil (mapcar (lambda (s)
- (if (string-match-p r s) s))
- collection)))))
- (mapcar (lambda (file)
- (cons (replace-regexp-in-string "^\./" "" file)
- (expand-file-name file)))
- ;; #15 improving handling of directories containing space
- collection)))
-
- (defun ffip--forward-line (lnum)
- "Forward LNUM lines."
- (if ffip-debug (message "ffip--forward-line called => %s" lnum))
- (when (and lnum (> lnum 0))
- (goto-char (point-min))
- (forward-line (1- lnum))))
-
- ;;;###autoload
- (defun ffip-find-files (keyword open-another-window &optional find-directory fn)
- "Use KEYWORD to find files.
- If OPEN-ANOTHER-WINDOW is t, the results are displayed in a new window.
- If FIND-DIRECTORY is t, only search directories. FN is callback.
- This function is the API to find files."
- (let* (cands lnum file root)
- ;; extract line num if exists
- (when (and keyword (stringp keyword)
- (string-match "^\\(.*\\):\\([0-9]+\\):?$" keyword))
- (setq lnum (string-to-number (match-string 2 keyword)))
- (setq keyword (match-string 1 keyword)))
-
- (setq cands (ffip-project-search keyword find-directory))
- (cond
- ((> (length cands) 0)
- (setq root (file-name-nondirectory (directory-file-name (ffip-get-project-root-directory))))
- (ffip-completing-read
- (format "Find in %s/: " root)
- cands
- `(lambda (file)
- ;; only one item in project files
- (if (listp file) (setq file (cdr file)))
- (if ,find-directory
- (if (quote ,open-another-window)
- (dired-other-window file)
- (switch-to-buffer (dired file)))
- ;; open file
- (if (quote ,open-another-window)
- (find-file-other-window file)
- (find-file file))
- ;; goto line if needed
- (ffip--forward-line ,lnum)
- (if ,fn (funcall ,fn file))))))
- (t
- (message "Nothing found!")))))
-
- (defun ffip--prepare-root-data-for-project-file (root)
- "Prepare data for ROOT."
- (cons 'ffip-project-root root))
-
- (defun ffip--read-selected ()
- (buffer-substring-no-properties (region-beginning) (region-end)))
-
- (defun ffip-read-keyword ()
- "Read keyword from selected text or user input."
- (let* ((hint (if ffip-use-rust-fd "Enter regex (or press ENTER):"
- "Enter keyword (or press ENTER):"))
- rlt)
- (cond
- ((region-active-p)
- (setq ffip-filename-history (add-to-list 'ffip-filename-history
- (ffip--read-selected)))
- (setq rlt (ffip--read-selected)))
- (t
- (setq rlt (read-from-minibuffer hint nil nil nil 'ffip-filename-history))))
- (if rlt (string-trim rlt) rlt)))
-
- ;;;###autoload
- (defun ffip-create-project-file ()
- "Create or Append .dir-locals.el to set up per directory.
- You can move .dir-locals.el to root directory.
- See (info \"(Emacs) Directory Variables\") for details."
- (interactive)
- (let* ((root (read-directory-name "Project root directory?" default-directory))
- (file (if (and root (file-exists-p root))
- (concat (file-name-as-directory root) ".dir-locals.el"))))
- (when file
- (with-temp-buffer
- (let ((print-level nil) (print-length nil) sexp (rlt '(a)))
- (cond
- ;; modify existing .dir-locals.el
- ((file-exists-p file)
- (let (sub-sexp new-sub-sexp)
- (setq sexp (ffip--read-file-text file))
- ;; valid .dir-locals.el
- (when sexp
- ;; the list for nil
- (setq sub-sexp (assoc nil sexp))
- (cond
- ;; `(nil (prop1 . val1) (prop2 . val2))' exists
- (sub-sexp
- ;; remove (ffip-project-root . "/path/file")
- (if (assoc 'ffip-project-root sub-sexp)
- (setq new-sub-sexp (delete (assoc 'ffip-project-root sub-sexp) sub-sexp))
- (setq new-sub-sexp sub-sexp))
- (add-to-list 'new-sub-sexp (ffip--prepare-root-data-for-project-file root) t)
- ;; update sexp
- (setq sexp (delete sub-sexp sexp))
- (add-to-list 'sexp new-sub-sexp))
- (t
- ;; add `(nil (ffip-project-root . "path/file"))'
- (add-to-list 'sexp (list nil (ffip--prepare-root-data-for-project-file root))))))
- ))
- (t
- ;; a new .dir-locals.el
- (setq sexp (list (list nil (ffip--prepare-root-data-for-project-file root))))))
- (when sexp
- (insert (format "%S" sexp))
- (write-file file)
- (message "%s created." file)))))))
-
- ;;;###autoload
- (defun ffip-current-full-filename-match-pattern-p (regex)
- "Is current full file name (including directory) match the REGEX?"
- (let* ((dir (if (buffer-file-name) (buffer-file-name) "")))
- (string-match-p regex dir)))
-
- ;;;###autoload
- (defun find-file-in-project (&optional open-another-window)
- "More powerful and efficient `find-file-in-project-by-selected' is recommended.
-
- Prompt with a completing list of all files in the project to find one.
- If OPEN-ANOTHER-WINDOW is not nil, the file will be opened in new window.
- The project's scope is defined as the first directory containing
- a `ffip-project-file' whose value is \".git\" by default.
- You can override this by setting the variable `ffip-project-root'."
- (interactive "P")
- (ffip-find-files nil open-another-window))
-
- (defun ffip-file-name-relative-p (filename)
- "Is FILENAME relative?"
- (if (string-match-p ffip-relative-path-pattern filename) t))
-
- ;;;###autoload
- (defun find-file-in-project-at-point (&optional open-another-window)
- "Find file whose name is guessed around point.
- If OPEN-ANOTHER-WINDOW is not nil, the file will be opened in new window."
- (interactive "P")
- (let* ((fn (or (and (region-active-p) (ffip--read-selected))
- (thing-at-point 'filename)
- (thing-at-point 'symbol)
- (read-string "No file name at point. Please provide file name:")))
- ;; could be a path
- (ffip-match-path-instead-of-filename t)
- tfn)
- (cond
- (fn
- (cond
- ((file-name-absolute-p fn)
- ;; absolute path
- (cond
- ((file-exists-p fn)
- ;; if file has absolute path and file exists, open it directly
- (if open-another-window (find-file-other-window fn)
- (find-file fn)))
- (t
- ;; well, search by file name
- (let* ((ffip-match-path-instead-of-filename nil))
- (ffip-find-files (file-name-nondirectory fn) open-another-window)))))
- ((and (ffip-file-name-relative-p fn)
- (file-exists-p (setq tfn (file-truename fn))))
- ;; file has relative path and file exist
- (if open-another-window (find-file-other-window tfn)
- (find-file tfn)))
- (t
- ;; strip prefix "../../" or "././" from file name
- (setq tfn (replace-regexp-in-string ffip-relative-path-pattern "" fn))
- (ffip-find-files tfn open-another-window))))
- (t
- (message "No file name is provided.")))))
-
- (defun ffip-parent-directory (level directory)
- "Return LEVEL up parent directory of DIRECTORY."
- (let* ((rlt directory))
- (while (and (> level 0) (not (string= "" rlt)))
- (setq rlt (file-name-directory (directory-file-name rlt)))
- (setq level (1- level)))
- (if (string= "" rlt) (setq rlt nil))
- rlt))
-
- ;;;###autoload
- (defun find-file-in-current-directory (&optional level)
- "Search fil in current directory or LEVEL up parent directory."
- (interactive "P")
- (unless level (setq level 0))
- (let* ((ffip-project-root (ffip-parent-directory level default-directory)))
- (find-file-in-project nil)))
-
- ;;;###autoload
- (defun find-file-in-project-by-selected (&optional open-another-window)
- "Same as `find-file-in-project' but more powerful and faster.
- It use string from selected region to search files in the project.
- If no region is selected, you could provide a keyword.
-
- Keyword could be ANY part of the file's full path and support wildcard.
- For example, to find /home/john/proj1/test.js, below keywords are valid:
- - test.js
- - roj1/tes
- - john*test
-
- If keyword contains line number like \"hello.txt:32\" or \"hello.txt:32:\",
- we will move to that line in opened file.
-
- If keyword is empty, it behaves same as `find-file-in-project'.
-
- If OPEN-ANOTHER-WINDOW is not nil, the file will be opened in new window."
- (interactive "P")
- (ffip-find-files (ffip-read-keyword) open-another-window))
-
- ;;;###autoload
- (defun ffip-insert-file ()
- "Insert contents of file in current buffer.
- The file name is selected interactively from candidates in project."
- (interactive)
- (let* ((cands (ffip-project-search (ffip-read-keyword) nil))
- root)
- (when (> (length cands) 0)
- (setq root (file-name-nondirectory (directory-file-name (ffip-get-project-root-directory))))
- (ffip-completing-read
- (format "Read file in %s/: " root)
- cands
- `(lambda (file)
- ;; only one item in project files
- (if (listp file) (setq file (cdr file)))
- (insert-file file))))))
-
- ;;;###autoload
- (defun find-file-with-similar-name (&optional open-another-window)
- "Use base name of current file as keyword which could be further stripped.
- by `ffip-strip-file-name-regex'.
- If OPEN-ANOTHER-WINDOW is not nil, the file will be opened in new window."
- (interactive "P")
- (when buffer-file-name
- (let* ((keyword (concat (file-name-base buffer-file-name) ".*") ))
- (if ffip-strip-file-name-regex
- (setq keyword (replace-regexp-in-string ffip-strip-file-name-regex
- ""
- keyword)))
- (ffip-find-files keyword open-another-window))))
-
- ;;;###autoload
- (defun find-file-in-current-directory-by-selected (&optional open-another-window)
- "Like `find-file-in-project-by-selected' but search current directory.
- If OPEN-ANOTHER-WINDOW is not nil, the file will be opened in new window."
- (interactive "P")
- (let* ((ffip-project-root default-directory))
- (find-file-in-project-by-selected open-another-window)))
-
- ;;;###autoload
- (defun find-relative-path(&optional find-directory)
- "Find file/directory and copy its relative path into `kill-ring'.
- Optional prefix FIND-DIRECTORY copy the directory path; file path by default.
-
- You can set `ffip-find-relative-path-callback' to format the string before copying,
- (setq ffip-find-relative-path-callback 'ffip-copy-reactjs-import)
- (setq ffip-find-relative-path-callback 'ffip-copy-org-file-link)"
- (interactive "P")
- (let* ((cands (ffip-project-search (ffip-read-keyword) find-directory))
- root)
- (cond
- ((> (length cands) 0)
- (setq root (file-name-nondirectory (directory-file-name (ffip-get-project-root-directory))))
- (ffip-completing-read
- (format "Find in %s/: " root)
- cands
- `(lambda (p)
- ;; only one item in project files
- (if (listp p) (setq p (cdr p)))
- (if ,find-directory
- (setq p (file-name-as-directory p)))
- (setq p (file-relative-name p (file-name-directory buffer-file-name)))
- (funcall ffip-find-relative-path-callback p))))
- (t
- (message "Nothing found!")))))
-
- ;;;###autoload
- (defun find-directory-in-project-by-selected (&optional open-another-window)
- "Similar to `find-file-in-project-by-selected'.
- Use string from selected region to find directory in the project.
- If no region is selected, you need provide keyword.
-
- Keyword could be directory's base-name only or parent-directory+base-name
- For example, to find /home/john/proj1/test, below keywords are valid:
- - test
- - roj1/test
- - john*test
-
- If OPEN-ANOTHER-WINDOW is not nil, the file will be opened in new window."
- (interactive "P")
- (ffip-find-files (ffip-read-keyword) open-another-window t))
-
- ;;;###autoload
- (defalias 'ffip 'find-file-in-project)
-
-
- (defun ffip-path (candidate)
- "Get path from ivy CANDIDATE."
- (let* ((default-directory (ffip-project-root)))
- (file-truename (if (consp candidate) (cdr candidate)
- candidate))))
-
- (defun ffip-split-window-api (split-fn mv-fn ratio)
- "Use SPLIT-FN to split window and focus on new window by MV-FN.
- Window split in RATIO."
- (let* (ratio-val
- (keyword (if ffip-split-window-without-asking-for-keyword ""
- (ffip-read-keyword)))
- (cands (ffip-project-search keyword nil))
- (file (if (= 1 (length cands)) (ffip-path (car cands))
- (ffip-path (ffip-completing-read "Find file: " cands))))
- (buf (if (and file (file-exists-p file)) (find-file-noselect file)
- (other-buffer))))
- (cond
- (ratio
- (setq ratio-val (cdr (assoc ratio ffip-window-ratio-alist)))
- (funcall split-fn (floor (/ (window-body-width)
- (1+ ratio-val)))))
- (t
- (funcall split-fn)))
- (set-window-buffer (next-window) buf)
- (if (or (not ratio-val)
- (>= ratio-val 1))
- (funcall mv-fn))))
-
- ;;;###autoload
- (defun ffip-split-window-horizontally (&optional ratio)
- "Find&Open file in horizontal split window.
- New window size is looked up in `ffip-window-ratio-alist' by RATIO.
- Keyword to search new file is selected text or user input."
- (interactive "P")
- (ffip-split-window-api 'split-window-horizontally 'windmove-right ratio))
-
- ;;;###autoload
- (defun ffip-split-window-vertically (&optional ratio)
- "Find&Open file in vertical split window.
- New window size is looked up in `ffip-window-ratio-alist' by RATIO.
- Keyword to search new file is selected text or user input."
- (interactive "P")
- (ffip-split-window-api 'split-window-vertically 'windmove-down ratio))
-
- ;;;###autoload
- (defun ffip-diff-quit ()
- "Quit."
- (interactive)
- ;; kill buffer instead of bury it
- (quit-window t))
-
- ;;;###autoload
- (defun ffip-diff-find-file (&optional open-another-window)
- "File file(s) in current hunk.
- If OPEN-ANOTHER-WINDOW is not nil, the file will be opened in new window."
- (interactive "P")
- (let* ((files (mapcar 'file-name-nondirectory (diff-hunk-file-names)))
- (alnum 0)
- (blnum 0)
- (regex "\\(?:\\*\\{15\\}.*\n\\)?[-@* ]*\\([0-9,]+\\)\\([ acd+]+\\([0-9,]+\\)\\)?"))
-
- (save-excursion
- (diff-beginning-of-hunk t)
- (when (looking-at regex)
- (setq alnum (string-to-number (match-string 1)))
- (setq blnum (string-to-number (match-string 3)))))
-
- (cond
- ((and (> (length files) 1)
- (string= (nth 0 files) (nth 1 files)))
- (ffip-find-files (nth 0 files)
- open-another-window
- nil
- `(lambda (opened-file)
- ;; use line number in new file since there
- ;; is only one file name candidate
- (ffip--forward-line ,blnum))))
- (t
- (run-hook-with-args 'ffip-diff-find-file-before-hook)
- (ffip-find-files files
- open-another-window
- nil
- (lambda (opened-file)
- (cond
- ((string= (file-name-nondirectory opened-file) (nth 0 files))
- (ffip--forward-line alnum))
- (t
- (ffip--forward-line blnum)))))))))
-
- (defvar ffip-diff-mode-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map diff-mode-map)
- ;; EVIL friendly. ffip-diff-mode is read-only
- (define-key map "K" 'diff-hunk-prev)
- (define-key map "J" 'diff-hunk-next)
- (define-key map "P" 'diff-file-prev)
- (define-key map "N" 'diff-file-next)
- (define-key map [remap diff-goto-source] 'ffip-diff-find-file)
- map)
- "Mode map based on `diff-mode-map'.")
-
- (define-derived-mode ffip-diff-mode diff-mode "ffip"
- "Show diff/patch."
- (setq buffer-read-only t)
- (setq truncate-lines t)
- (use-local-map ffip-diff-mode-map))
-
- (defun ffip-show-content-in-diff-mode (content)
- "Insert CONTENT into *ffip-diff* buffer."
- (cond
- ((and content (not (string= content "")))
- (let (rlt-buf)
- (if (get-buffer "*ffip-diff*")
- (kill-buffer "*ffip-diff*"))
- (setq rlt-buf (get-buffer-create "*ffip-diff*"))
- (save-current-buffer
- (switch-to-buffer-other-window rlt-buf)
- (set-buffer rlt-buf)
- (erase-buffer)
- (insert content)
- (ffip-diff-mode)
- (goto-char (point-min)))))
- (t
- (message "Output is empty!"))))
-
- (defun ffip-diff-execute-backend (backend)
- "Execute BACKEND."
- (if backend
- (cond
- ;; shell command
- ((stringp backend)
- (ffip-show-content-in-diff-mode (ffip-shell-command-to-string backend)))
- ;; command
- ((functionp backend)
- (ffip-show-content-in-diff-mode (funcall backend)))
- ;; lisp expression
- ((consp backend)
- (ffip-show-content-in-diff-mode (funcall `(lambda () ,backend)))))))
-
- (defun ffip-backend-description (backend)
- "Get BACKEND description."
- (let* (rlt)
- (cond
- ;; shell command
- ((stringp backend)
- (setq rlt backend))
- ;; command
- ((functionp backend)
- (setq rlt (symbol-name backend)))
- ;; lisp expression
- ((consp backend)
- ;; (cons "description" actual-backend)
- (if (stringp (car backend))
- (setq rlt (car backend))
- (setq rlt "unknown"))))
- rlt))
-
- ;;;###autoload
- (defun ffip-show-diff-internal (&optional num)
- "Show the diff output by executing selected `ffip-diff-backends'.
- NUM is the index selected backend from `ffip-diff-backends'.
- NUM is zero based whose default value is zero."
- (interactive "P")
- (cond
- ((or (not num) (< num 0))
- (setq num 0))
- ((> num (length ffip-diff-backends))
- (setq num (1- (length ffip-diff-backends)))))
-
- (let* ((backend (nth num ffip-diff-backends)))
- (if (and (consp backend)
- (stringp (car backend)))
- (setq backend (cdr backend)))
- (ffip-diff-execute-backend backend)))
-
- ;;;###autoload
- (defun ffip-show-diff-by-description (&optional num)
- "Show the diff output by executing selected `ffip-diff-backends'.
- NUM is the backend index of `ffip-diff-backends'.
- If NUM is not nil, the corresponding backend is executed directly."
- (interactive "P")
- (cond
- (num
- (ffip-show-diff-internal num))
- (t
- (let* (descriptions
- (i 0))
- ;; format backend descriptions
- (dolist (b ffip-diff-backends)
- (add-to-list 'descriptions
- (format "%s: %s"
- i
- (ffip-backend-description b)) t)
- (setq i (+ 1 i)))
- (ffip-completing-read
- "Run diff backend:"
- descriptions
- `(lambda (d)
- (if (string-match "^\\([0-9]+\\): " d)
- (ffip-show-diff-internal (string-to-number (match-string 1 d))))))))))
-
- (defalias 'ffip-show-diff 'ffip-show-diff-by-description)
-
- (defadvice read-file-name (around ffip-read-file-name-hack activate)
- (cond
- (ffip-read-file-name-hijacked-p
- ;; only hack read-file-name once
- (setq ffip-read-file-name-hijacked-p nil)
- (let* ((args (ad-get-args 0))
- (file-name (file-name-nondirectory (nth 2 args)))
- (default-directory (ffip-project-root))
- (cands (ffip-project-search file-name nil default-directory))
- (rlt (if cands (ffip-completing-read "Files: " cands))))
- (when rlt
- (setq rlt (file-truename rlt))
- (run-hook-with-args 'ffip-diff-apply-hunk-hook rlt)
- (setq ad-return-value rlt))))
- (t
- ad-do-it)))
-
- ;;;###autoload
- (defun ffip-diff-apply-hunk (&optional reverse)
- "Apply current hunk in `diff-mode'. Try to locate the file to patch.
- Similar to `diff-apply-hunk' but smarter.
- Please read documentation of `diff-apply-hunk' to get more details.
- If REVERSE is t, applied patch is reverted."
- (interactive "P")
- (cond
- ((derived-mode-p 'diff-mode)
- (setq ffip-read-file-name-hijacked-p t)
- (diff-apply-hunk reverse)
- (setq ffip-read-file-name-hijacked-p nil))
- (t
- (message "This command only run in `diff-mode' and `ffip-diff-mode'."))))
-
- ;; safe locals
- (progn
- (put 'ffip-diff-backends 'safe-local-variable 'listp)
- (put 'ffip-patterns 'safe-local-variable 'listp)
- (put 'ffip-prune-patterns 'safe-local-variable 'listp)
- (put 'ffip-ignore-filenames 'safe-local-variable 'listp)
- (put 'ffip-match-path-instead-of-filename 'safe-local-variable 'booleanp)
- (put 'ffip-project-file 'safe-local-variable 'stringp)
- (put 'ffip-strip-file-name-regex 'safe-local-variable 'stringp)
- (put 'ffip-project-root 'safe-local-variable 'stringp))
-
- (provide 'find-file-in-project)
- ;;; find-file-in-project.el ends here
|