-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ffpc.el
144 lines (117 loc) · 5.17 KB
/
ffpc.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
;;; ffpc.el --- Find file in project or current directory -*- lexical-binding: t; -*-
;; Copyright (C) 2022-2024 Shen, Jen-Chieh
;; Author: Shen, Jen-Chieh <jcs090218@gmail.com>
;; Maintainer: Shen, Jen-Chieh <jcs090218@gmail.com>
;; URL: https://github.com/jcs-elpa/ffpc
;; Version: 0.1.0
;; Package-Requires: ((emacs "28.1") (dash "2.12.0") (f "0.20.0"))
;; Keywords: lisp
;; This file is not part of GNU Emacs.
;; 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 of the License, 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 this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Find file in project or current directory.
;;
;;; Code:
(require 'cl-lib)
(require 'project)
(require 'dash)
(require 'f)
(defgroup ffpc nil
"Find file in project or current directory."
:prefix "ffpc-"
:group 'tools
:link '(url-link :tag "Repository" "https://github.com/jcs-elpa/ffpc"))
;;
;; (@* "Util" )
;;
(defun ffpc--project-root ()
"Return project directory path."
(when-let ((current (project-current))) (project-root current)))
(defun ffpc--listify (obj)
"Turn OBJ to list."
(if (listp obj) obj (list obj)))
(defun ffpc-directories-ignored-dir (path &optional rec)
"Find all directories in PATH by ignored common directories with FN and REC."
(let ((dirs (f-directories path)) valid-dirs final-dirs)
(dolist (dir dirs)
(unless (member (f-filename (f-slash dir)) project-vc-ignores)
(push dir valid-dirs)))
(when rec
(dolist (dir valid-dirs)
(push (ffpc-directories-ignored-dir dir rec) final-dirs)))
(setq valid-dirs (reverse valid-dirs)
final-dirs (reverse final-dirs))
(-flatten (append valid-dirs final-dirs))))
(defun ffpc-files-ignored-dir (path &optional fn rec)
"Find all files in PATH by ignored common directories with FN and REC."
(let ((dirs (append (list path) (ffpc-directories-ignored-dir path rec)))
files)
(dolist (dir dirs)
(when-let ((fs (ignore-errors (f-files dir fn))))
(push fs files)))
(-flatten (reverse files))))
;;
;; (@* "API" )
;;
(defun ffpc--match-file (regexs it)
"Return if IT matches REGEXS."
(let ((regexs (ffpc--listify regexs))
(it (f-filename it)))
(cl-some (lambda (regex) (string-match-p regex it)) regexs)))
;;;###autoload
(defun ffpc-select-file-current-dir (filename title)
"Find FILENAME in current directory.
Argument FILENAME accept regular expression string.
Argument TITLE is a string used when there are more than one matches."
(let* ((target-files
(ffpc-files-ignored-dir default-directory
(lambda (file)
(ffpc--match-file filename file))))
(target-files-len (length target-files)))
(when (zerop target-files-len)
(user-error "[ERROR] No file '%s' found in the current directory" filename))
(if (= target-files-len 1)
(nth 0 target-files) ; If only one file found, just get that file.
(completing-read title target-files)))) ; Get the selected file.
;;;###autoload
(defun ffpc-select-file-in-project (filename title)
"Find FILENAME in current project.
Argument FILENAME accept regular expression string.
Argument TITLE is a string used when there are more than one matches."
(let* ((project-dir (ffpc--project-root))
;; Do the find file only when the project directory exists.
(target-files (when project-dir
(ffpc-files-ignored-dir project-dir
(lambda (file)
(ffpc--match-file filename file))
t)))
(target-files-len (when target-files (length target-files))))
(unless target-files-len
(user-error "[ERROR] No file '%s' found in project, make sure the project root exists" filename))
(if (= target-files-len 1)
(nth 0 target-files) ; If only one file found, just get that file.
(completing-read title target-files)))) ; Get the selected file.
;;;###autoload
(defun ffpc-project-or-current-dir (filename title)
"Find the file from project root, if not found find it in current directory.
Return full path if found, else error prompt. FILENAME to search in project
or current directory. TITLE search uses regexp, meaning it could found
multiple files at a time. We need a title to present which file to select."
(if-let ((filepath
(or (ignore-errors (ffpc-select-file-current-dir filename title))
(ignore-errors (ffpc-select-file-in-project filename title)))))
filepath
(user-error
"No valid file found in either current directory or project, %s" filename)))
(provide 'ffpc)
;;; ffpc.el ends here