1 ;;; locate.el --- interface to the locate command
3 ;; Copyright (C) 1996, 1998, 2001, 2002, 2003, 2004,
4 ;; 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
6 ;; Author: Peter Breton <pbreton@cs.umb.edu>
7 ;; Keywords: unix files
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
26 ;; Search a database of files and use dired commands on the result.
28 ;; Locate.el provides an interface to a program which searches a
29 ;; database of file names. By default, this program is the GNU locate
30 ;; command, but it could also be the BSD-style find command, or even a
31 ;; user specified command.
33 ;; To use the BSD-style "fast find", or any other shell command of the
36 ;; SHELLPROGRAM Name-to-find
38 ;; set the variable `locate-command' in your .emacs file.
40 ;; To use a more complicated expression, create a function which
41 ;; takes a string (the name to find) as input and returns a list.
42 ;; The first element should be the command to be executed, the remaining
43 ;; elements should be the arguments (including the name to find). Then put
45 ;; (setq locate-make-command-line 'my-locate-command-line)
47 ;; in your .emacs, using the name of your function in place of
48 ;; my-locate-command-line.
50 ;; You should make sure that whichever command you use works correctly
51 ;; from a shell prompt. GNU locate and BSD find expect the file databases
52 ;; to either be in standard places or located via environment variables.
53 ;; If the latter, make sure these environment variables are set in
54 ;; your emacs process.
56 ;; Locate-mode assumes that each line output from the locate-command
57 ;; consists exactly of a file name, possibly preceded or trailed by
58 ;; whitespace. If your file database has other information on the line (for
59 ;; example, the file size), you will need to redefine the function
60 ;; `locate-get-file-positions' to return a list consisting of the first
61 ;; character in the file name and the last character in the file name.
63 ;; To use locate-mode, simply type M-x locate and then the string
64 ;; you wish to find. You can use almost all of the dired commands in
65 ;; the resulting *Locate* buffer. It is worth noting that your commands
66 ;; do not, of course, affect the file database. For example, if you
67 ;; compress a file in the locate buffer, the actual file will be
68 ;; compressed, but the entry in the file database will not be
69 ;; affected. Consequently, the database and the filesystem will be out
70 ;; of sync until the next time the database is updated.
72 ;; The command `locate-with-filter' keeps only lines matching a
73 ;; regular expression; this is often useful to constrain a big search.
76 ;;;;; Building a database of files ;;;;;;;;;
78 ;; You can create a simple files database with a port of the Unix find command
79 ;; and one of the various Windows NT various scheduling utilities,
80 ;; for example the AT command from the NT Resource Kit, WinCron which is
81 ;; included with Microsoft FrontPage, or the shareware NTCron program.
83 ;; To set up a function which searches the files database, do something
86 ;; (defvar locate-fcodes-file "c:/users/peter/fcodes")
87 ;; (defvar locate-make-command-line 'nt-locate-make-command-line)
89 ;; (defun nt-locate-make-command-line (arg)
90 ;; (list "grep" "-i" arg locate-fcodes-file))
92 ;;;;;;;; ADVICE For dired-make-relative: ;;;;;;;;;
94 ;; For certain dired commands to work right, you should also include the
95 ;; following in your _emacs/.emacs:
97 ;; (defadvice dired-make-relative (before set-no-error activate)
98 ;; "For locate mode and Windows, don't return errors"
99 ;; (if (and (eq major-mode 'locate-mode)
100 ;; (memq system-type (list 'windows-nt 'ms-dos)))
104 ;; Otherwise, `dired-make-relative' will give error messages like
105 ;; "FILENAME: not in directory tree growing at /"
115 (defvar locate-current-filter nil
)
116 (defvar locate-local-filter nil
)
117 (defvar locate-local-search nil
)
118 (defvar locate-local-prompt nil
)
121 "Interface to the locate command."
125 (defcustom locate-command
"locate"
126 "Executable program for searching a database of files.
127 The Emacs commands `locate' and `locate-with-filter' use this.
128 The value should be a program that can be called from a shell
129 with one argument, SEARCH-STRING. The program determines which
130 database it searches. The output of the program should consist
131 of those file names in the database that match SEARCH-STRING,
132 listed one per line, possibly with leading or trailing
133 whitespace. If the output is in another form, you may have to
134 redefine the function `locate-get-file-positions'.
136 The program may interpret SEARCH-STRING as a literal string, a
137 shell pattern or a regular expression. The exact rules of what
138 constitutes a match may also depend on the program.
140 The standard value of this variable is \"locate\".
141 This program normally searches a database of all files on your
142 system, or of all files that you have access to. Consult the
143 documentation of that program for the details about how it determines
144 which file names match SEARCH-STRING. (Those details vary highly with
149 (defvar locate-history-list nil
150 "The history list used by the \\[locate] command.")
152 (defvar locate-grep-history-list nil
153 "The history list used by the \\[locate-with-filter] command.")
155 (defcustom locate-make-command-line
'locate-default-make-command-line
156 "Function used to create the locate command line.
157 The Emacs commands `locate' and `locate-with-filter' use this.
158 This function should take one argument, a string (the name to find)
159 and return a list of strings. The first element of the list should be
160 the name of a command to be executed by a shell, the remaining elements
161 should be the arguments to that command (including the name to find)."
165 (defcustom locate-buffer-name
"*Locate*"
166 "Name of the buffer to show results from the \\[locate] command."
170 (defcustom locate-fcodes-file nil
171 "File name for the database of file names used by `locate'.
172 If non-nil, `locate' uses this name in the header of the `*Locate*'
173 buffer. If nil, it mentions no file name in that header.
175 Just setting this variable does not actually change the database
176 that `locate' searches. The executive program that the Emacs
177 function `locate' uses, as given by the variables `locate-command'
178 or `locate-make-command-line', determines the database."
179 :type
'(choice (const :tag
"None" nil
) file
)
182 (defcustom locate-header-face nil
183 "Face used to highlight the locate header."
184 :type
'(choice (const :tag
"None" nil
) face
)
188 (defcustom locate-ls-subdir-switches
"-al"
189 "`ls' switches for inserting subdirectories in `*Locate*' buffers.
190 This should contain the \"-l\" switch, but not the \"-F\" or \"-b\" switches."
195 (defcustom locate-update-when-revert nil
196 "This option affects how the *Locate* buffer gets reverted.
197 If non-nil, offer to update the locate database when reverting that buffer.
198 \(Normally, you need to have root privileges for this to work. See the
199 option `locate-update-path'.)
200 If nil, reverting does not update the locate database."
205 (defcustom locate-update-command
"updatedb"
206 "The executable program used to update the locate database."
210 (defcustom locate-update-path
"/"
211 "The default directory from where `locate-update-command' is called.
212 Usually, root permissions are required to run that command. This
213 can be achieved by setting this option to \"/su::\" or \"/sudo::\"
214 \(if you have the appropriate authority). If your current user
215 permissions are sufficient to run the command, you can set this
221 (defcustom locate-prompt-for-command nil
222 "If non-nil, the `locate' command prompts for a command to run.
223 Otherwise, that behavior is invoked via a prefix argument.
225 Setting this option non-nil actually inverts the meaning of a prefix arg;
226 that is, with a prefix arg, you get the default behavior."
232 (defun locate-default-make-command-line (search-string)
233 (list locate-command search-string
))
235 (defun locate-word-at-point ()
237 (buffer-substring-no-properties
239 (skip-chars-backward "-a-zA-Z0-9.")
242 (skip-chars-forward "-a-zA-Z0-9.")
243 (skip-chars-backward "." pt
)
246 ;; Function for use in interactive declarations.
247 (defun locate-prompt-for-search-string ()
248 (if (or (and current-prefix-arg
249 (not locate-prompt-for-command
))
250 (and (not current-prefix-arg
) locate-prompt-for-command
))
251 (let ((locate-cmd (funcall locate-make-command-line
"")))
252 (read-from-minibuffer
253 "Run locate (like this): "
255 (concat (car locate-cmd
) " "
256 (mapconcat 'identity
(cdr locate-cmd
) " "))
257 (+ 2 (length (car locate-cmd
))))
258 nil nil
'locate-history-list
))
259 (let* ((default (locate-word-at-point))
261 (read-from-minibuffer
262 (if (> (length default
) 0)
263 (format "Locate (default %s): " default
)
265 nil nil nil
'locate-history-list default t
)))
266 (and (equal input
"") default
267 (setq input default
))
271 (defun locate (search-string &optional filter arg
)
272 "Run the program `locate', putting results in `*Locate*' buffer.
273 Pass it SEARCH-STRING as argument. Interactively, prompt for SEARCH-STRING.
274 With prefix arg, prompt for the exact shell command to run instead.
276 This program searches for those file names in a database that match
277 SEARCH-STRING and normally outputs all matching absolute file names,
278 one per line. The database normally consists of all files on your
279 system, or of all files that you have access to. Consult the
280 documentation of the program for the details about how it determines
281 which file names match SEARCH-STRING. (Those details vary highly with
284 You can specify another program for this command to run by customizing
285 the variables `locate-command' or `locate-make-command-line'.
287 The main use of FILTER is to implement `locate-with-filter'. See
288 the docstring of that function for its meaning.
290 ARG is the interactive prefix arg."
293 (locate-prompt-for-search-string)
297 (if (equal search-string
"")
298 (error "Please specify a filename to search for"))
299 (let* ((locate-cmd-list (funcall locate-make-command-line search-string
))
300 (locate-cmd (car locate-cmd-list
))
301 (locate-cmd-args (cdr locate-cmd-list
))
303 (or (and arg
(not locate-prompt-for-command
))
304 (and (not arg
) locate-prompt-for-command
)))
307 ;; Find the Locate buffer
308 (save-window-excursion
309 (set-buffer (get-buffer-create locate-buffer-name
))
311 (let ((inhibit-read-only t
)
312 (buffer-undo-list t
))
315 (setq locate-current-filter filter
)
316 (set (make-local-variable 'locate-local-search
) search-string
)
317 (set (make-local-variable 'locate-local-filter
) filter
)
318 (set (make-local-variable 'locate-local-prompt
) run-locate-command
)
320 (if run-locate-command
321 (shell-command search-string locate-buffer-name
)
322 (apply 'call-process locate-cmd nil t nil locate-cmd-args
))
325 (locate-filter-output filter
))
327 (locate-do-setup search-string
)
329 (and (not (string-equal (buffer-name) locate-buffer-name
))
330 (switch-to-buffer-other-window locate-buffer-name
))
332 (run-hooks 'dired-mode-hook
)
333 (dired-next-line 3) ;move to first matching file.
334 (run-hooks 'locate-post-command-hook
)
339 (defun locate-with-filter (search-string filter
&optional arg
)
340 "Run the executable program `locate' with a filter.
341 This function is similar to the function `locate', which see.
342 The difference is that, when invoked interactively, the present function
343 prompts for both SEARCH-STRING and FILTER. It passes SEARCH-STRING
344 to the locate executable program. It produces a `*Locate*' buffer
345 that lists only those lines in the output of the locate program that
346 contain a match for the regular expression FILTER; this is often useful
347 to constrain a big search.
349 ARG is the interactive prefix arg, which has the same effect as in `locate'.
351 When called from Lisp, this function is identical with `locate',
352 except that FILTER is not optional."
355 (locate-prompt-for-search-string)
356 (read-from-minibuffer "Filter: " nil nil
357 nil
'locate-grep-history-list
)
359 (locate search-string filter arg
))
361 (defun locate-filter-output (filter)
362 "Filter output from the locate command."
363 (goto-char (point-min))
366 (defvar locate-mode-map nil
367 "Local keymap for Locate mode buffers.")
373 (setq locate-mode-map
(copy-keymap dired-mode-map
))
375 ;; Undefine Useless Dired Menu bars
376 (define-key locate-mode-map
[menu-bar Dired
] 'undefined
)
377 (define-key locate-mode-map
[menu-bar subdir
] 'undefined
)
379 (define-key locate-mode-map
[menu-bar mark executables
] 'undefined
)
380 (define-key locate-mode-map
[menu-bar mark directory
] 'undefined
)
381 (define-key locate-mode-map
[menu-bar mark directories
] 'undefined
)
382 (define-key locate-mode-map
[menu-bar mark symlinks
] 'undefined
)
384 (define-key locate-mode-map
[M-mouse-2
] 'locate-mouse-view-file
)
385 (define-key locate-mode-map
"\C-c\C-t" 'locate-tags
)
387 (define-key locate-mode-map
"l" 'locate-do-redisplay
)
388 (define-key locate-mode-map
"U" 'dired-unmark-all-files
)
389 (define-key locate-mode-map
"V" 'locate-find-directory
)
392 ;; This variable is used to indent the lines and then to search for
394 (defconst locate-filename-indentation
4
395 "The amount of indentation for each file.")
397 (defun locate-get-file-positions ()
398 "Return list of start and end of the file name on the current line.
399 This is a list of two buffer positions.
401 You should only call this function on lines that contain a file name
402 listed by the locate program. Inside inserted subdirectories, or if
403 there is no file name on the current line, the return value is
404 meaningless. You can check whether the current line contains a file
405 listed by the locate program, using the function
406 `locate-main-listing-line-p'."
407 (list (+ locate-filename-indentation
408 (line-beginning-position))
409 ;; Assume names end at the end of the line.
410 (line-end-position)))
413 (defun locate-current-line-number ()
414 "Return the current line number, as an integer."
415 (+ (count-lines (point-min) (point))
416 (if (eq (current-column) 0)
420 ;; You should only call this function on lines that contain a file name
421 ;; listed by the locate program. Inside inserted subdirectories, or if
422 ;; there is no file name on the current line, the return value is
423 ;; meaningless. You can check whether the current line contains a file
424 ;; listed by the locate program, using the function
425 ;; `locate-main-listing-line-p'.
426 (defun locate-get-filename ()
427 (let ((pos (locate-get-file-positions))
428 (lineno (locate-current-line-number)))
429 (and (not (eq lineno
1))
431 (buffer-substring (elt pos
0) (elt pos
1)))))
433 (defun locate-main-listing-line-p ()
434 "Return t if current line contains a file name listed by locate.
435 This function returns nil if the current line either contains no
436 file name or is inside a subdirectory."
439 (looking-at (concat "."
440 (make-string (1- locate-filename-indentation
) ?\s
)
441 "\\(/\\|[A-Za-z]:\\)"))))
443 (defun locate-mouse-view-file (event)
444 "In Locate mode, view a file, using the mouse."
447 (goto-char (posn-point (event-start event
)))
448 (if (locate-main-listing-line-p)
449 (view-file (locate-get-filename))
450 (message "This command only works inside main listing."))))
452 ;; Define a mode for locate
453 ;; Default directory is set to "/" so that dired commands, which
454 ;; expect to be in a tree, will work properly
455 (defun locate-mode ()
456 "Major mode for the `*Locate*' buffer made by \\[locate].
458 In that buffer, you can use almost all the usual dired bindings.
459 \\[locate-find-directory] visits the directory of the file on the current line.
461 Operating on listed files works, but does not always
462 automatically update the buffer as in ordinary Dired.
463 This is true both for the main listing and for subdirectories.
464 Reverting the buffer using \\[revert-buffer] deletes all subdirectories.
465 Specific `locate-mode' commands, such as \\[locate-find-directory],
466 do not work in subdirectories.
469 ;; Not to be called interactively.
470 (kill-all-local-variables)
471 ;; Avoid clobbering this variable
472 (make-local-variable 'dired-subdir-alist
)
473 (use-local-map locate-mode-map
)
474 (setq major-mode
'locate-mode
476 default-directory
"/"
479 (dired-alist-add-1 default-directory
(point-min-marker))
480 (set (make-local-variable 'dired-directory
) "/")
481 (set (make-local-variable 'dired-subdir-switches
) locate-ls-subdir-switches
)
482 (setq dired-switches-alist nil
)
483 (make-local-variable 'directory-listing-before-filename-regexp
)
484 ;; This should support both Unix and Windoze style names
485 (setq directory-listing-before-filename-regexp
487 (make-string (1- locate-filename-indentation
) ?\s
)
488 "\\(/\\|[A-Za-z]:\\)\\|"
489 (default-value 'directory-listing-before-filename-regexp
)))
490 (make-local-variable 'dired-actual-switches
)
491 (setq dired-actual-switches
"")
492 (make-local-variable 'dired-permission-flags-regexp
)
493 (setq dired-permission-flags-regexp
495 (make-string (1- locate-filename-indentation
) ?\s
)
497 (default-value 'dired-permission-flags-regexp
)))
498 (make-local-variable 'revert-buffer-function
)
499 (setq revert-buffer-function
'locate-update
)
500 (set (make-local-variable 'page-delimiter
) "\n\n")
501 (run-mode-hooks 'locate-mode-hook
))
503 (defun locate-do-setup (search-string)
504 (goto-char (point-min))
507 ;; Nothing returned from locate command?
510 (kill-buffer locate-buffer-name
)
511 (if locate-current-filter
512 (error "Locate: no match for %s in database using filter %s"
513 search-string locate-current-filter
)
514 (error "Locate: no match for %s in database" search-string
))))
516 (locate-insert-header search-string
)
519 (insert-char ?\s locate-filename-indentation t
)
520 (locate-set-properties)
522 (goto-char (point-min)))
524 (defun locate-set-properties ()
526 (let ((pos (locate-get-file-positions)))
527 (dired-insert-set-properties (elt pos
0) (elt pos
1)))))
529 (defun locate-insert-header (search-string)
530 ;; There needs to be a space before `Matches, because otherwise,
531 ;; `*!" would erase the `M'. We can not use two spaces, or the line
532 ;; would mistakenly fit `dired-subdir-regexp'.
533 (let ((locate-format-string " /:\n Matches for %s")
535 (concat " *Matches for \\(" (regexp-quote search-string
) "\\)"))
536 (locate-format-args (list search-string
))
539 (and locate-fcodes-file
540 (setq locate-format-string
541 (concat locate-format-string
" in %s")
543 (concat locate-regexp-match
545 (regexp-quote locate-fcodes-file
)
548 (append (list locate-fcodes-file
) locate-format-args
)))
550 (and locate-current-filter
551 (setq locate-format-string
552 (concat locate-format-string
" using filter %s")
554 (concat locate-regexp-match
557 (regexp-quote locate-current-filter
)
560 (append (list locate-current-filter
) locate-format-args
)))
562 (setq locate-format-string
563 (concat locate-format-string
":\n\n")
565 (concat locate-regexp-match
":\n"))
567 (insert (apply 'format locate-format-string
(reverse locate-format-args
)))
570 (goto-char (point-min))
572 (if (not (looking-at locate-regexp-match
))
574 (add-text-properties (match-beginning 1) (match-end 1)
575 (list 'face locate-header-face
))
576 (and (match-beginning 2)
577 (add-text-properties (match-beginning 2) (match-end 2)
578 (list 'face locate-header-face
)))
579 (and (match-beginning 3)
580 (add-text-properties (match-beginning 3) (match-end 3)
581 (list 'face locate-header-face
)))
584 (defun locate-tags ()
585 "Visit a tags table in `*Locate*' mode."
587 (if (locate-main-listing-line-p)
588 (let ((tags-table (locate-get-filename)))
589 (and (y-or-n-p (format "Visit tags table %s? " tags-table
))
590 (visit-tags-table tags-table
)))
591 (message "This command only works inside main listing.")))
593 ;; From Stephen Eglen <stephen@cns.ed.ac.uk>
594 (defun locate-update (ignore1 ignore2
)
595 "Revert the *Locate* buffer.
596 If `locate-update-when-revert' is non-nil, offer to update the
597 locate database using the shell command in `locate-update-command'."
598 (let ((locate-buffer-name (buffer-name))
599 (locate-prompt-for-command locate-local-prompt
))
600 (and locate-update-when-revert
601 (yes-or-no-p "Update locate database (may take a few seconds)? ")
602 ;; `expand-file-name' is used in order to autoload Tramp if
603 ;; necessary. It cannot be loaded when `default-directory'
605 (let ((default-directory (expand-file-name locate-update-path
)))
606 (shell-command locate-update-command
)))
607 (locate locate-local-search locate-local-filter
)))
609 ;;; Modified three functions from `dired.el':
610 ;;; dired-find-directory,
611 ;;; dired-find-directory-other-window
612 ;;; dired-get-filename
614 (defun locate-find-directory ()
615 "Visit the directory of the file mentioned on this line."
617 (if (locate-main-listing-line-p)
618 (let ((directory-name (locate-get-dirname)))
619 (if (file-directory-p directory-name
)
620 (find-file directory-name
)
621 (if (file-symlink-p directory-name
)
622 (error "Directory is a symlink to a nonexistent target")
623 (error "Directory no longer exists; run `updatedb' to update database"))))
624 (message "This command only works inside main listing.")))
626 (defun locate-find-directory-other-window ()
627 "Visit the directory of the file named on this line in other window."
629 (if (locate-main-listing-line-p)
630 (find-file-other-window (locate-get-dirname))
631 (message "This command only works inside main listing.")))
633 ;; You should only call this function on lines that contain a file name
634 ;; listed by the locate program. Inside inserted subdirectories, or if
635 ;; there is no file name on the current line, the return value is
636 ;; meaningless. You can check whether the current line contains a file
637 ;; listed by the locate program, using the function
638 ;; `locate-main-listing-line-p'.
639 (defun locate-get-dirname ()
640 "Return the directory name of the file mentioned on this line."
641 (let (file (filepos (locate-get-file-positions)))
642 (if (setq file
(buffer-substring (nth 0 filepos
) (nth 1 filepos
)))
644 ;; Get rid of the mouse-face property that file names have.
645 (set-text-properties 0 (length file
) nil file
)
646 (setq file
(file-name-directory file
))
647 ;; Unquote names quoted by ls or by dired-insert-directory.
648 ;; Using read to unquote is much faster than substituting
649 ;; \007 (4 chars) -> ^G (1 char) etc. in a lisp loop.
653 ;; some ls -b don't escape quotes, argh!
654 ;; This is not needed for GNU ls, though.
655 (or (dired-string-replace-match
656 "\\([^\\]\\|\\`\\)\"" file
"\\1\\\\\"" nil t
)
659 (and file buffer-file-coding-system
660 (not file-name-coding-system
)
661 (setq file
(encode-coding-string file buffer-file-coding-system
)))
664 ;; Only for GNU locate
665 (defun locate-in-alternate-database (search-string database
)
666 "Run the GNU locate program, using an alternate database.
668 This command only works if you use GNU locate. It does not work
669 properly if `locate-prompt-for-command' is set to t. In that
670 case, you can just run the regular `locate' command and specify
671 the database on the command line."
676 (read-from-minibuffer "Locate: " nil nil
677 nil
'locate-history-list
))
678 (read-file-name "Locate using Database: " )
680 (or (file-exists-p database
)
681 (error "Database file %s does not exist" database
))
682 (let ((locate-make-command-line
683 (function (lambda (string)
685 (list (concat "--database="
686 (expand-file-name database
))
688 (locate search-string
)))
690 (defun locate-do-redisplay (&optional arg test-for-subdir
)
691 "Like `dired-do-redisplay', but adapted for `*Locate*' buffers."
693 (if (string= (dired-current-directory) "/")
694 (message "This command only works in subdirectories.")
695 (let ((dired-actual-switches locate-ls-subdir-switches
))
696 (dired-do-redisplay arg test-for-subdir
))))
700 ;; arch-tag: 60c4d098-b5d5-4b3c-a3e0-51a2e9f43898
701 ;;; locate.el ends here