mirror of https://github.com/Chizi123/.emacs.d.git

Chizi123
2018-11-21 7074318d7ab58aca124f590c42fd820e8eb258a5
commit | author | age
5cb5f7 1 ;;; helm-locate.el --- helm interface for locate. -*- lexical-binding: t -*-
C 2
3 ;; Copyright (C) 2012 ~ 2018 Thierry Volpiatto <thierry.volpiatto@gmail.com>
4
5 ;; This program is free software; you can redistribute it and/or modify
6 ;; it under the terms of the GNU General Public License as published by
7 ;; the Free Software Foundation, either version 3 of the License, or
8 ;; (at your option) any later version.
9
10 ;; This program is distributed in the hope that it will be useful,
11 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ;; GNU General Public License for more details.
14
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 ;; NOTE for WINDOZE users:
19 ;; You have to install Everything with his command line interface here:
20 ;; http://www.voidtools.com/download.php
21
22 ;;; Code:
23
24 (require 'cl-lib)
25 (require 'helm)
26 (require 'helm-types)
27 (require 'helm-help)
28
29
30 (defgroup helm-locate nil
31   "Locate related Applications and libraries for Helm."
32   :group 'helm)
33
34 (defcustom helm-locate-db-file-regexp "m?locate\.db$"
35   "Default regexp to match locate database.
36 If nil Search in all files."
37   :type  'string
38   :group 'helm-locate)
39
40 (defcustom helm-ff-locate-db-filename "locate.db"
41   "The basename of the locatedb file you use locally in your directories.
42 When this is set and `helm' find such a file in the directory from
43 where you launch locate, it will use this file and will not prompt you
44 for a db file.
45 Note that this happen only when locate is launched with a prefix arg."
46   :group 'helm-locate
47   :type 'string)
48
49 (defcustom helm-locate-command nil
50   "A list of arguments for locate program.
51
52 Helm will calculate a default value for your system on startup unless
53 `helm-locate-command' is non-nil, here the default values it will use
54 according to your system:
55
56 Gnu/linux:     \"locate %s -e -A --regex %s\"
57 berkeley-unix: \"locate %s %s\"
58 windows-nt:    \"es %s %s\"
59 Others:        \"locate %s %s\"
60
61 This string will be passed to format so it should end with `%s'.
62 The first format spec is used for the \"-i\" value of locate/es,
63 So don't set it directly but use `helm-locate-case-fold-search'
64 for this.
65
66 The last option must be the one preceding pattern i.e \"-r\" or \"--regex\".
67
68 You will be able to pass other options such as \"-b\" or \"l\"
69 during helm invocation after entering pattern only when multi matching,
70 not when fuzzy matching.
71
72 Note that the \"-b\" option is added automatically by helm when
73 var `helm-locate-fuzzy-match' is non-nil and switching back from
74 multimatch to fuzzy matching (this is done automatically when a space
75 is detected in pattern)."
76   :type 'string
77   :group 'helm-locate)
78
79 (defcustom helm-locate-create-db-command
80   "updatedb -l 0 -o '%s' -U '%s'"
81   "Command used to create a locale locate db file."
82   :type 'string
83   :group 'helm-locate)
84
85 (defcustom helm-locate-case-fold-search helm-case-fold-search
86   "It have the same meaning as `helm-case-fold-search'.
87 The -i option of locate will be used depending of value of
88 `helm-pattern' when this is set to 'smart.
89 When nil \"-i\" will not be used at all.
90 and when non--nil it will always be used.
91 NOTE: the -i option of the \"es\" command used on windows does
92 the opposite of \"locate\" command."
93   :group 'helm-locate
94   :type 'symbol)
95
96 (defcustom helm-locate-fuzzy-match nil
97   "Enable fuzzy matching in `helm-locate'.
98 Note that when this is enabled searching is done on basename."
99   :group 'helm-locate
100   :type 'boolean)
101
102 (defcustom helm-locate-fuzzy-sort-fn
103   #'helm-locate-default-fuzzy-sort-fn
104   "Default fuzzy matching sort function for locate."
105   :group 'helm-locate
106   :type 'boolean)
107
108 (defcustom helm-locate-project-list nil
109   "A list of directories, your projects.
110 When set, allow browsing recursively files in all
111 directories of this list with `helm-projects-find-files'."
112   :group 'helm-locate
113   :type '(repeat string))
114
115 (defcustom helm-locate-recursive-dirs-command "locate -i -e -A --regex '^%s' '%s.*$'"
116   "Command used for recursive directories completion in `helm-find-files'.
117
118 For Windows and `es' use something like \"es -r ^%s.*%s.*$\"
119
120 The two format specs are mandatory.
121
122 If for some reasons you can't use locate because your filesystem
123 doesn't have a data base, you can use find command from findutils but
124 be aware that it will be much slower, see `helm-find-files' embebded
125 help for more infos."
126   :type 'string
127   :group 'helm-files)
128
129
130 (defvar helm-locate-map
131   (let ((map (make-sparse-keymap)))
132     (set-keymap-parent map helm-generic-files-map)
133     (define-key map (kbd "DEL") 'helm-delete-backward-no-update)
134     map))
135
136 (defface helm-locate-finish
137     '((t (:foreground "Green")))
138   "Face used in mode line when locate process is finish."
139   :group 'helm-locate)
140
141
142 (defun helm-ff-find-locatedb (&optional from-ff)
143   "Try to find if a local locatedb file is available.
144 The search is done in `helm-ff-default-directory' or
145 fall back to `default-directory' if FROM-FF is nil."
146   (helm-aif (and helm-ff-locate-db-filename
147                  (locate-dominating-file
148                   (or (and from-ff
149                            helm-ff-default-directory)
150                       default-directory)
151                   helm-ff-locate-db-filename))
152       (expand-file-name helm-ff-locate-db-filename it)))
153
154 (defun helm-locate-create-db-default-function (db-name directory)
155   "Default function used to create a locale locate db file.
156 Argument DB-NAME name of the db file.
157 Argument DIRECTORY root of file system subtree to scan."
158   (format helm-locate-create-db-command
159           db-name (expand-file-name directory)))
160
161 (defvar helm-locate-create-db-function
162   #'helm-locate-create-db-default-function
163   "Function used to create a locale locate db file.
164 It should receive the same arguments as
165 `helm-locate-create-db-default-function'.")
166
167 (defun helm-locate-1 (&optional localdb init from-ff default)
168   "Generic function to run Locate.
169 Prefix arg LOCALDB when (4) search and use a local locate db file when it
170 exists or create it, when (16) force update of existing db file
171 even if exists.
172 It have no effect when locate command is 'es'.
173 INIT is a string to use as initial input in prompt.
174 See `helm-locate-with-db' and `helm-locate'."
175   (require 'helm-mode)
176   (helm-locate-set-command)
177   (let ((pfn (lambda (candidate)
178                  (if (file-directory-p candidate)
179                      (message "Error: The locate Db should be a file")
180                    (if (= (shell-command
181                            (funcall helm-locate-create-db-function
182                                     candidate
183                                     helm-ff-default-directory))
184                           0)
185                        (message "New locatedb file `%s' created" candidate)
186                      (error "Failed to create locatedb file `%s'" candidate)))))
187         (locdb (and localdb
188                     (not (string-match "^es" helm-locate-command))
189                     (or (and (equal '(4) localdb)
190                              (helm-ff-find-locatedb from-ff))
191                         (helm-read-file-name
192                          "Create Locate Db file: "
193                          :initial-input (expand-file-name "locate.db"
194                                                           (or helm-ff-default-directory
195                                                               default-directory))
196                          :preselect helm-locate-db-file-regexp
197                          :test (lambda (x)
198                                    (if helm-locate-db-file-regexp
199                                        ;; Select only locate db files and directories
200                                        ;; to allow navigation.
201                                        (or (string-match
202                                             helm-locate-db-file-regexp x)
203                                            (file-directory-p x))
204                                      x)))))))
205     (when (and locdb (or (equal localdb '(16))
206                          (not (file-exists-p locdb))))
207       (funcall pfn locdb))
208     (helm-locate-with-db (and localdb locdb) init default)))
209
210 (defun helm-locate-set-command ()
211   "Setup `helm-locate-command' if not already defined."
212   (unless helm-locate-command
213     (setq helm-locate-command
214           (cl-case system-type
215             (gnu/linux "locate %s -e -A --regex %s")
216             (berkeley-unix "locate %s %s")
217             (windows-nt "es %s %s")
218             (t "locate %s %s")))))
219
220 (defun helm-locate-initial-setup ()
221   (require 'helm-for-files)
222   (helm-locate-set-command))
223
224 (defvar helm-file-name-history nil)
225 (defun helm-locate-with-db (&optional db initial-input default)
226   "Run locate -d DB.
227 If DB is not given or nil use locate without -d option.
228 Argument DB can be given as a string or list of db files.
229 Argument INITIAL-INPUT is a string to use as initial-input.
230 See also `helm-locate'."
231   (require 'helm-files)
232   (when (and db (stringp db)) (setq db (list db)))
233   (helm-locate-set-command)
234   (let ((helm-locate-command
235          (if db
236              (replace-regexp-in-string
237               "locate"
238               (format (if helm-locate-fuzzy-match
239                           "locate -b -d '%s'" "locate -d '%s'")
240                       (mapconcat 'identity
241                                  ;; Remove eventually
242                                  ;; marked directories by error.
243                                  (cl-loop for i in db
244                                        unless (file-directory-p i)
245                                        ;; expand-file-name to resolve
246                                        ;; abbreviated fnames not
247                                        ;; expanding inside single
248                                        ;; quotes i.e. '%s'.
249                                        collect (expand-file-name i))
250                                  ":"))
251               helm-locate-command)
252            (if (and helm-locate-fuzzy-match
253                     (not (string-match-p "\\`locate -b" helm-locate-command)))
254                (replace-regexp-in-string
255                 "\\`locate" "locate -b" helm-locate-command)
256                helm-locate-command))))
257     (setq helm-file-name-history (mapcar 'helm-basename file-name-history))
258     (helm :sources 'helm-source-locate
259           :buffer "*helm locate*"
260           :ff-transformer-show-only-basename nil
261           :input initial-input
262           :default default
263           :history 'helm-file-name-history)))
264
265 (defun helm-locate-init ()
266   "Initialize async locate process for `helm-source-locate'."
267   (let* ((locate-is-es (string-match "\\`es" helm-locate-command))
268          (real-locate (string-match "\\`locate" helm-locate-command))
269          (case-sensitive-flag (if locate-is-es "-i" ""))
270          (ignore-case-flag (if (or locate-is-es
271                                    (not real-locate)) "" "-i"))
272          (args (helm-mm-split-pattern helm-pattern))
273          (cmd (format helm-locate-command
274                       (cl-case helm-locate-case-fold-search
275                         (smart (let ((case-fold-search nil))
276                                  (if (string-match "[[:upper:]]" helm-pattern)
277                                      case-sensitive-flag
278                                      ignore-case-flag)))
279                         (t (if helm-locate-case-fold-search
280                                ignore-case-flag
281                                case-sensitive-flag)))
282                       (concat
283                        ;; The pattern itself.
284                        (shell-quote-argument (car args)) " "
285                        ;; Possible locate args added
286                        ;; after pattern, don't quote them.
287                        (mapconcat 'identity (cdr args) " "))))
288          (default-directory (if (file-directory-p default-directory)
289                                 default-directory "/")))
290     (helm-log "Starting helm-locate process")
291     (helm-log "Command line used was:\n\n%s"
292               (concat ">>> " (propertize cmd 'face 'font-lock-comment-face) "\n\n"))
293     (prog1
294         (start-process-shell-command
295          "locate-process" helm-buffer
296          cmd)
297       (set-process-sentinel
298        (get-buffer-process helm-buffer)
299        (lambda (process event)
300          (let* ((err (process-exit-status process))
301                 (noresult (= err 1)))
302            (cond (noresult
303                   (with-helm-buffer
304                     (unless (cdr helm-sources)
305                       (insert (concat "* Exit with code 1, no result found,"
306                                       " command line was:\n\n "
307                                       cmd)))))
308                  ((string= event "finished\n")
309                   (when (and helm-locate-fuzzy-match
310                              (not (string-match-p "\\s-" helm-pattern)))
311                     (helm-redisplay-buffer))
312                   (with-helm-window
313                     (setq mode-line-format
314                           '(" " mode-line-buffer-identification " "
315                             (:eval (format "L%s" (helm-candidate-number-at-point))) " "
316                             (:eval (propertize
317                                     (format "[Locate process finished - (%s results)]"
318                                             (max (1- (count-lines
319                                                       (point-min) (point-max)))
320                                                  0))
321                                     'face 'helm-locate-finish))))
322                     (force-mode-line-update)))
323                  (t
324                   (helm-log "Error: Locate %s"
325                             (replace-regexp-in-string "\n" "" event))))))))))
326
327 (defun helm-locate-default-fuzzy-sort-fn (candidates)
328   "Default sort function for files in fuzzy matching.
329 Sort is done on basename of CANDIDATES."
330   (helm-fuzzy-matching-default-sort-fn-1 candidates nil t))
331
332 (defclass helm-locate-override-inheritor (helm-type-file) ())
333
334 (defclass helm-locate-source (helm-source-async helm-locate-override-inheritor)
335   ((init :initform 'helm-locate-initial-setup)
336    (candidates-process :initform 'helm-locate-init)
337    (requires-pattern :initform 3)
338    (history :initform 'helm-file-name-history)
339    (persistent-action :initform 'helm-ff-kill-or-find-buffer-fname)
340    (candidate-number-limit :initform 9999)
341    (redisplay :initform (progn helm-locate-fuzzy-sort-fn))
342    (group :initform 'helm-locate)))
343
344 ;; Override helm-type-file class keymap.
345 (defmethod helm--setup-source :after ((source helm-locate-override-inheritor))
346   (setf (slot-value source 'keymap) helm-locate-map))
347
348 (defvar helm-source-locate
349   (helm-make-source "Locate" 'helm-locate-source
350     :pattern-transformer 'helm-locate-pattern-transformer
351     ;; :match-part is only used here to tell helm which part
352     ;; of candidate to highlight.
353     :match-part (lambda (candidate)
354                   (if (or (string-match-p " -b\\'" helm-pattern)
355                           (and helm-locate-fuzzy-match
356                                (not (string-match "\\s-" helm-pattern))))
357                       (helm-basename candidate)
358                       candidate))))
359
360 (defun helm-locate-pattern-transformer (pattern)
361   (if helm-locate-fuzzy-match
362       ;; When fuzzy is enabled helm add "-b" option on startup.
363       (cond ((string-match-p " " pattern)
364              (when (string-match "\\`locate -b" helm-locate-command)
365                (setq helm-locate-command
366                      (replace-match "locate" t t helm-locate-command)))
367              pattern)
368             (t
369              (unless (string-match-p "\\`locate -b" helm-locate-command)
370                (setq helm-locate-command
371                      (replace-regexp-in-string
372                       "\\`locate" "locate -b" helm-locate-command)))
373              (helm--mapconcat-pattern pattern)))
374       pattern))
375
376 (defun helm-locate-find-dbs-in-projects (&optional update)
377   (let* ((pfn (lambda (candidate directory)
378                 (unless (= (shell-command
379                             (funcall helm-locate-create-db-function
380                                      candidate
381                                      directory))
382                            0)
383                   (error "Failed to create locatedb file `%s'" candidate)))))
384     (cl-loop for p in helm-locate-project-list
385              for db = (expand-file-name
386                        helm-ff-locate-db-filename
387                        (file-name-as-directory p))
388              if (and (null update) (file-exists-p db))
389              collect db
390              else do (funcall pfn db p)
391              and collect db)))
392
393 ;;; Directory completion for hff.
394 ;;
395 (defclass helm-locate-subdirs-source (helm-source-in-buffer)
396   ((basedir :initarg :basedir
397             :initform nil
398             :custom string)
399    (subdir :initarg :subdir
400            :initform nil
401            :custom 'string)
402    (data :initform #'helm-locate-init-subdirs)
403    (group :initform 'helm-locate)))
404
405 (defun helm-locate-init-subdirs ()
406   (with-temp-buffer
407     (call-process-shell-command
408      (format helm-locate-recursive-dirs-command
409          (if (string-match-p "\\`es" helm-locate-recursive-dirs-command)
410                  ;; Fix W32 paths.
411          (replace-regexp-in-string
412                   "/" "\\\\\\\\" (helm-attr 'basedir))
413                  (helm-attr 'basedir))
414          (helm-attr 'subdir))
415      nil t nil)
416     (buffer-string)))
417
418 ;;;###autoload
419 (defun helm-projects-find-files (update)
420   "Find files with locate in `helm-locate-project-list'.
421 With a prefix arg refresh the database in each project."
422   (interactive "P")
423   (helm-locate-set-command)
424   (cl-assert (and (string-match-p "\\`locate" helm-locate-command)
425                   (executable-find "updatedb"))
426              nil "Unsupported locate version")
427   (let ((dbs (helm-locate-find-dbs-in-projects update)))
428     (if dbs
429         (helm-locate-with-db dbs)
430         (user-error "No projects found, please setup `helm-locate-project-list'"))))
431
432 ;;;###autoload
433 (defun helm-locate (arg)
434   "Preconfigured `helm' for Locate.
435 Note: you can add locate options after entering pattern.
436 See 'man locate' for valid options and also `helm-locate-command'.
437
438 You can specify a local database with prefix argument ARG.
439 With two prefix arg, refresh the current local db or create it
440 if it doesn't exists.
441
442 To create a user specific db, use
443 \"updatedb -l 0 -o db_path -U directory\".
444 Where db_path is a filename matched by
445 `helm-locate-db-file-regexp'."
446   (interactive "P")
447   (helm-set-local-variable 'helm-async-outer-limit-hook
448                            (list (lambda ()
449                                    (when (and helm-locate-fuzzy-match
450                                               (not (string-match-p
451                                                     "\\s-" helm-pattern)))
452                                      (helm-redisplay-buffer)))))
453   (setq helm-ff-default-directory default-directory)
454   (helm-locate-1 arg nil nil (thing-at-point 'filename)))
455
456 (provide 'helm-locate)
457
458 ;; Local Variables:
459 ;; byte-compile-warnings: (not obsolete)
460 ;; coding: utf-8
461 ;; indent-tabs-mode: nil
462 ;; End:
463
464 ;;; helm-locate.el ends here