-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathellocate.el
190 lines (163 loc) · 6.87 KB
/
ellocate.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
;;; ellocate.el --- The locate command reimplemented in Emacs Lisp -*- lexical-binding: t -*-
;; Author: Sebastian Wålinder <[email protected]>
;; URL: https://github.com/walseb/ellocate
;; Version: 1.0
;; Package-Requires: ((emacs "25.1") (s "1.12.0") (f "0.20.0"))
;; Keywords: matching
;; This file 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.
;; For a full copy of the GNU General Public License
;; see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This package is a re-implementation of locate written in Emacs Lisp.
;; It recursively searches directories using the find command and then stores the
;; results in the database file specified in `ellocate-scan-dir'.
;; The function `ellocate' scans the directory as specified by `ellocate-scan-dirs'
;; and creates a database file to store the results in.
;; `ellocate-clear' clears all stored databases.
;; This forces `ellocate' to refresh its search entries. Do this if your
;; file-system has changed and you want that reflected in `ellocate'.
;;; Code:
(require 's)
(require 'f)
(require 'cl-lib)
(defgroup ellocate nil
"The locate command reimplemented in Emacs Lisp."
:prefix "ellocate-"
:group 'external
:link '(url-link :tag "GitHub" "https://github.com/walseb/ellocate"))
(defcustom ellocate-scan-dirs '(("~/" "~/ellocate-home-db")
("/mnt/" nil))
"A list of lists in this format:
'((path database-location) (path2 database-location2)).
If the database field is nil, ellocate will not save the database to
disk, but just store it in ram."
:type 'sexp
:group 'ellocate)
(defvar ellocate-gc-mem 80000000
"GC limit to set before running ellocate.
Set before running ellocate to avoid multiple garbage collections. If nil
don't modify `gc-cons-threshold'")
(defvar ellocate-scan-cache '()
"The variable where caches are stored inside Emacs for quick access.
Used to display the ellocate prompt quickly.")
(defvar ellocate-database-coding-system 'utf-8
"The coding system used by the database file.")
;; This PWD might be a problem https://stackoverflow.com/questions/246215/how-can-i-generate-a-list-of-files-with-their-absolute-path-in-linux
(defvar ellocate-find-command "find \"$PWD\" -name '.git' -prune -o -type f -print"
"Find command used to build the cache.")
;;;###autoload
(defun ellocate-clear-all ()
"Cleans all caches defined in `ellocate-scan-dirs'.
Next time you run `ellocate' you will need to rebuild the cache.
Run this if your file system has changed and you want ellocate to find your new files."
(interactive)
(setq ellocate-scan-cache nil)
(mapc (lambda (list)
(when (nth 1 list)
(delete-file (nth 1 list))))
ellocate-scan-dirs))
;;;###autoload
(defun ellocate-clear (&optional ignore-scope)
"Clears the cache corresponding to your current directory.
If IGNORE-SCOPE is non-nil, clear all caches instead of just the cache
corresponding to your current directory.
Next time you run `ellocate' in this directory it will need to
rebuild the cache.
Run this function if your file system has changed and you want `ellocate' to find your new files."
(interactive "P")
(if ignore-scope
(ellocate-clear-all)
;; Clear cache
(setq ellocate-scan-cache
(seq-remove (lambda (list)
(let* ((path-name (nth 0 list))
(db-name (nth 1 list)))
(if (and db-name (file-in-directory-p default-directory path-name))
t
nil)))
ellocate-scan-cache))
;; Clear databases on disk
(mapc (lambda (list)
(let* ((path-name (nth 0 list))
(db-name (nth 1 list)))
(when (and db-name (file-in-directory-p default-directory path-name))
(message (concat "Deleting database: " db-name))
(delete-file db-name))))
ellocate-scan-dirs)))
(defun ellocate-cache-dir (list)
"Caches directory in LIST to database also in LIST."
(let* ((default-directory (expand-file-name (nth 0 list)))
(cache (ignore-errors (expand-file-name (nth 1 list))))
(candidates (if (and cache (file-exists-p cache))
(f-read cache)
(shell-command-to-string ellocate-find-command))))
;; Write cache to file
(when (and cache (not (file-exists-p cache)))
(let ((coding-system-for-write (or coding-system-for-write
ellocate-database-coding-system)))
(write-region candidates nil cache)))
;; Store the cache in Emacs as a variable
(add-to-list 'ellocate-scan-cache `(,default-directory
,(split-string
candidates
"\n")))))
;;;###autoload
(defun ellocate (&optional ignore-scope)
"Displays any files below the current dir.
If IGNORE-SCOPE is non-nil, search the entire database instead of just every
file under the current directory."
(interactive "P")
(let* ((gc-cons-threshold (or ellocate-gc-mem gc-cons-threshold))
;; Load data from cached search corresponding to this default-directory
(search
(nth 1 (cl-find-if (lambda (list)
(file-in-directory-p
default-directory (nth 0 list)))
ellocate-scan-cache))))
(if search
(let ((dir (expand-file-name default-directory)))
(find-file
(ellocate-completing-read dir search ignore-scope)))
(let ((found-dir (cl-find-if
(lambda (list)
(file-in-directory-p (expand-file-name default-directory) (nth 0 list)))
ellocate-scan-dirs)))
(if found-dir
(ellocate-cache-dir found-dir)
(when (fboundp 'counsel-file-jump)
(counsel-file-jump))))
;; Re-run ellocate on the newly found files
(ellocate))))
;;;###autoload
(defun ellocate-all ()
"Find all files in current database.
This can also be done by running ellocate with a universal argument
but I figured it would be good to make it more explicit."
(interactive)
(ellocate t))
(defun ellocate-completing-read (dir candidates ignore-scope)
"Run completing read on CANDIDATES without sorting.
Only candidates inside DIR are shown if IGNORE-SCOPE is nil."
(let* ((presorted-completions
(if ignore-scope
candidates
(seq-filter (lambda (string) (s-starts-with-p dir string)) candidates)))
(completion-table
;; Don't give up here! What this lambda does is described here:
;; https://emacs.stackexchange.com/questions/41801/how-to-stop-completing-read-ivy-completing-read-from-sorting?rq=1
(lambda (string pred action)
(if (eq action 'metadata)
'(metadata (display-sort-function . identity)
(cycle-sort-function . identity))
(complete-with-action
action presorted-completions string pred)))))
(completing-read "Find: " completion-table)))
(provide 'ellocate)
;;; ellocate.el ends here