;;; filecache.el --- find files using a pre-loaded cache
-;;
+
+;; Copyright (C) 1996, 2000-2014 Free Software Foundation, Inc.
+
;; Author: Peter Breton <pbreton@cs.umb.edu>
;; Created: Sun Nov 10 1996
;; Keywords: convenience
-;;
-;; Copyright (C) 1996, 2000, 2001, 2002, 2003, 2004,
-;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
;; This file is part of GNU Emacs.
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs 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.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
-(eval-when-compile
- (require 'find-lisp))
-
(defgroup file-cache nil
"Find files using a pre-loaded cache."
:group 'files
;; User-modifiable variables
(defcustom file-cache-filter-regexps
+ ;; These are also used in buffers containing lines of file names,
+ ;; so the end-of-name is matched with $ rather than \\'.
(list "~$" "\\.o$" "\\.exe$" "\\.a$" "\\.elc$" ",v$" "\\.output$"
"\\.$" "#$" "\\.class$")
- "*List of regular expressions used as filters by the file cache.
+ "List of regular expressions used as filters by the file cache.
File names which match these expressions will not be added to the cache.
Note that the functions `file-cache-add-file' and `file-cache-add-file-list'
do not use this variable."
:group 'file-cache)
(defcustom file-cache-find-command "find"
- "*External program used by `file-cache-add-directory-using-find'."
+ "External program used by `file-cache-add-directory-using-find'."
:type 'string
:group 'file-cache)
(defcustom file-cache-find-command-posix-flag 'not-defined
- "*Set to t, if `file-cache-find-command' handles wildcards POSIX style.
+ "Set to t, if `file-cache-find-command' handles wildcards POSIX style.
This variable is automatically set to nil or non-nil
if it has the initial value `not-defined' whenever you first
call the `file-cache-add-directory-using-find'.
:group 'file-cache)
(defcustom file-cache-locate-command "locate"
- "*External program used by `file-cache-add-directory-using-locate'."
+ "External program used by `file-cache-add-directory-using-locate'."
:type 'string
:group 'file-cache)
:group 'file-cache)
(defcustom file-cache-completion-ignore-case
- (if (memq system-type (list 'ms-dos 'windows-nt 'cygwin))
+ (if (memq system-type '(ms-dos windows-nt cygwin))
t
completion-ignore-case)
"If non-nil, file-cache completion should ignore case.
Defaults to the value of `completion-ignore-case'."
- :type 'sexp
- :group 'file-cache
- )
+ :type 'boolean
+ :group 'file-cache)
(defcustom file-cache-case-fold-search
- (if (memq system-type (list 'ms-dos 'windows-nt 'cygwin))
+ (if (memq system-type '(ms-dos windows-nt cygwin))
t
case-fold-search)
"If non-nil, file-cache completion should ignore case.
Defaults to the value of `case-fold-search'."
- :type 'sexp
- :group 'file-cache
- )
+ :type 'boolean
+ :group 'file-cache)
(defcustom file-cache-ignore-case
- (memq system-type (list 'ms-dos 'windows-nt 'cygwin))
+ (memq system-type '(ms-dos windows-nt cygwin))
"Non-nil means ignore case when checking completions in the file cache.
Defaults to nil on DOS and Windows, and t on other systems."
- :type 'sexp
- :group 'file-cache
- )
+ :type 'boolean
+ :group 'file-cache)
(defvar file-cache-multiple-directory-message nil)
(defvar file-cache-last-completion nil)
(defvar file-cache-alist nil
- "Internal data structure to hold cache of file names.")
-
-(defvar file-cache-completions-keymap nil
+ "Internal data structure to hold cache of file names.
+It is a list of entries of the form (FILENAME DIRNAME1 DIRNAME2 ...)
+where FILENAME is a file name component and the entry represents N
+files of names DIRNAME1/FILENAME, DIRNAME2/FILENAME, ...")
+
+(defvar file-cache-completions-keymap
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map completion-list-mode-map)
+ (define-key map [mouse-2] 'file-cache-choose-completion)
+ (define-key map "\C-m" 'file-cache-choose-completion)
+ map)
"Keymap for file cache completions buffer.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions to add files to the cache
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun file-cache--read-list (file op-prompt)
+ (let* ((fun (if file 'read-file-name 'read-directory-name))
+ (type (if file "file" "directory"))
+ (prompt-1 (concat op-prompt " " type ": "))
+ (prompt-2 (concat op-prompt " another " type "?"))
+ (continue t)
+ result)
+ (while continue
+ (push (funcall fun prompt-1 nil nil t) result)
+ (setq continue (y-or-n-p prompt-2)))
+ (nreverse result)))
+
;;;###autoload
(defun file-cache-add-directory (directory &optional regexp)
- "Add DIRECTORY to the file cache.
-If the optional REGEXP argument is non-nil, only files which match it will
-be added to the cache."
- (interactive "DAdd files from directory: ")
+ "Add all files in DIRECTORY to the file cache.
+If called from Lisp with a non-nil REGEXP argument is non-nil,
+only add files whose names match REGEXP."
+ (interactive (list (read-directory-name "Add files from directory: "
+ nil nil t)
+ nil))
;; Not an error, because otherwise we can't use load-paths that
;; contain non-existent directories.
- (if (not (file-accessible-directory-p directory))
- (message "Directory %s does not exist" directory)
+ (when (file-accessible-directory-p directory)
(let* ((dir (expand-file-name directory))
- (dir-files (directory-files dir t regexp))
- )
+ (dir-files (directory-files dir t regexp)))
;; Filter out files we don't want to see
- (mapc
- '(lambda (file)
- (if (file-directory-p file)
- (setq dir-files (delq file dir-files))
- (mapc
- '(lambda (regexp)
- (if (string-match regexp file)
- (setq dir-files (delq file dir-files))))
- file-cache-filter-regexps)))
- dir-files)
+ (dolist (file dir-files)
+ (if (file-directory-p file)
+ (setq dir-files (delq file dir-files))
+ (dolist (regexp file-cache-filter-regexps)
+ (if (string-match regexp file)
+ (setq dir-files (delq file dir-files))))))
(file-cache-add-file-list dir-files))))
;;;###autoload
-(defun file-cache-add-directory-list (directory-list &optional regexp)
- "Add DIRECTORY-LIST (a list of directory names) to the file cache.
+(defun file-cache-add-directory-list (directories &optional regexp)
+ "Add DIRECTORIES (a list of directory names) to the file cache.
+If called interactively, read the directory names one by one.
If the optional REGEXP argument is non-nil, only files which match it
-will be added to the cache. Note that the REGEXP is applied to the files
-in each directory, not to the directory list itself."
- (interactive "XAdd files from directory list: ")
- (mapcar
- '(lambda (dir) (file-cache-add-directory dir regexp))
- directory-list))
-
-(defun file-cache-add-file-list (file-list)
- "Add FILE-LIST (a list of files names) to the file cache."
- (interactive "XFile List: ")
- (mapcar 'file-cache-add-file file-list))
+will be added to the cache. Note that the REGEXP is applied to the
+files in each directory, not to the directory list itself."
+ (interactive (list (file-cache--read-list nil "Add")))
+ (dolist (dir directories)
+ (file-cache-add-directory dir regexp))
+ (let ((n (length directories)))
+ (message "Filecache: cached file names from %d director%s."
+ n (if (= n 1) "y" "ies"))))
+
+(defun file-cache-add-file-list (files)
+ "Add FILES (a list of file names) to the file cache.
+If called interactively, read the file names one by one."
+ (interactive (list (file-cache--read-list t "Add")))
+ (dolist (f files)
+ (file-cache-add-file f))
+ (let ((n (length files)))
+ (message "Filecache: cached %d file name%s."
+ n (if (= n 1) "" "s"))))
;; Workhorse function
(defun file-cache-add-file (file)
"Add FILE to the file cache."
(interactive "fAdd File: ")
- (if (not (file-exists-p file))
- (message "Filecache: file %s does not exist" file)
- (let* ((file-name (file-name-nondirectory file))
- (dir-name (file-name-directory file))
- (the-entry (assoc-string
- file-name file-cache-alist
- file-cache-ignore-case))
- )
- ;; Does the entry exist already?
- (if the-entry
- (if (or (and (stringp (cdr the-entry))
- (string= dir-name (cdr the-entry)))
- (and (listp (cdr the-entry))
- (member dir-name (cdr the-entry))))
- nil
- (setcdr the-entry (append (list dir-name) (cdr the-entry)))
- )
- ;; If not, add it to the cache
- (setq file-cache-alist
- (cons (cons file-name (list dir-name))
- file-cache-alist)))
- )))
+ (setq file (file-truename file))
+ (unless (file-exists-p file)
+ (error "Filecache: file %s does not exist" file))
+ (let* ((file-name (file-name-nondirectory file))
+ (dir-name (file-name-directory file))
+ (the-entry (assoc-string file-name file-cache-alist
+ file-cache-ignore-case)))
+ (cond ((null the-entry)
+ ;; If the entry wasn't in the cache, add it.
+ (push (list file-name dir-name) file-cache-alist)
+ (if (called-interactively-p 'interactive)
+ (message "Filecache: cached file name %s." file)))
+ ((not (member dir-name (cdr the-entry)))
+ (setcdr the-entry (cons dir-name (cdr the-entry)))
+ (if (called-interactively-p 'interactive)
+ (message "Filecache: cached file name %s." file)))
+ (t
+ (if (called-interactively-p 'interactive)
+ (message "Filecache: %s is already cached." file))))))
;;;###autoload
(defun file-cache-add-directory-using-find (directory)
string)
(file-cache-add-from-file-cache-buffer))
+(autoload 'find-lisp-find-files "find-lisp")
+
;;;###autoload
(defun file-cache-add-directory-recursively (dir &optional regexp)
"Adds DIR and any subdirectories to the file-cache.
-This function does not use any external programs
+This function does not use any external programs.
If the optional REGEXP argument is non-nil, only files which match it
-will be added to the cache. Note that the REGEXP is applied to the files
-in each directory, not to the directory list itself."
+will be added to the cache. Note that the REGEXP is applied to the
+files in each directory, not to the directory list itself."
(interactive "DAdd directory: ")
- (require 'find-lisp)
(mapcar
- (function
- (lambda(file)
- (or (file-directory-p file)
- (let (filtered)
- (mapc
- (function
- (lambda(regexp)
- (and (string-match regexp file)
- (setq filtered t))
- ))
- file-cache-filter-regexps)
- filtered)
- (file-cache-add-file file))))
- (find-lisp-find-files dir (if regexp regexp "^"))))
+ (lambda (file)
+ (or (file-directory-p file)
+ (let (filtered)
+ (dolist (regexp file-cache-filter-regexps)
+ (and (string-match regexp file)
+ (setq filtered t)))
+ filtered)
+ (file-cache-add-file file)))
+ (find-lisp-find-files dir (or regexp "^"))))
(defun file-cache-add-from-file-cache-buffer (&optional regexp)
"Add any entries found in the file cache buffer.
Each entry matches the regular expression `file-cache-buffer-default-regexp'
or the optional REGEXP argument."
(set-buffer file-cache-buffer)
- (mapc
- (function (lambda (elt)
- (goto-char (point-min))
- (delete-matching-lines elt)))
- file-cache-filter-regexps)
+ (dolist (elt file-cache-filter-regexps)
+ (goto-char (point-min))
+ (delete-matching-lines elt))
(goto-char (point-min))
(let ((full-filename))
(while (re-search-forward
;; This clears *all* files with the given name
(defun file-cache-delete-file (file)
- "Delete FILE from the file cache."
+ "Delete FILE (a relative file name) from the file cache.
+Return nil if FILE was not in the file cache, non-nil otherwise."
(interactive
(list (completing-read "Delete file from cache: " file-cache-alist)))
- (setq file-cache-alist
- (delq (assoc-string file file-cache-alist file-cache-ignore-case)
- file-cache-alist)))
-
-(defun file-cache-delete-file-list (file-list)
- "Delete FILE-LIST (a list of files) from the file cache."
- (interactive "XFile List: ")
- (mapcar 'file-cache-delete-file file-list))
+ (let ((elt (assoc-string file file-cache-alist file-cache-ignore-case)))
+ (setq file-cache-alist (delq elt file-cache-alist))
+ elt))
+
+(defun file-cache-delete-file-list (files &optional message)
+ "Delete FILES (a list of files) from the file cache.
+If called interactively, read the file names one by one.
+If MESSAGE is non-nil, or if called interactively, print a
+message reporting the number of file names deleted."
+ (interactive (list (file-cache--read-list t "Uncache") t))
+ (let ((n 0))
+ (dolist (f files)
+ (if (file-cache-delete-file f)
+ (setq n (1+ n))))
+ (when message
+ (message "Filecache: uncached %d file name%s."
+ n (if (= n 1) "" "s")))))
(defun file-cache-delete-file-regexp (regexp)
"Delete files matching REGEXP from the file cache."
(interactive "sRegexp: ")
(let ((delete-list))
- (mapc '(lambda (elt)
- (and (string-match regexp (car elt))
- (setq delete-list (cons (car elt) delete-list))))
- file-cache-alist)
- (file-cache-delete-file-list delete-list)
- (message "Filecache: deleted %d files from file cache"
- (length delete-list))))
+ (dolist (elt file-cache-alist)
+ (and (string-match regexp (car elt))
+ (push (car elt) delete-list)))
+ (file-cache-delete-file-list delete-list)))
(defun file-cache-delete-directory (directory)
"Delete DIRECTORY from the file cache."
(interactive "DDelete directory from file cache: ")
(let ((dir (expand-file-name directory))
- (result 0))
- (mapc
- '(lambda (entry)
- (if (file-cache-do-delete-directory dir entry)
- (setq result (1+ result))))
- file-cache-alist)
- (if (zerop result)
- (error "Filecache: no entries containing %s found in cache" directory)
- (message "Filecache: deleted %d entries" result))))
+ (n 0))
+ (dolist (entry file-cache-alist)
+ (if (file-cache-do-delete-directory dir entry)
+ (setq n (1+ n))))
+ (message "Filecache: uncached %d file name%s."
+ n (if (= n 1) "" "s"))))
(defun file-cache-do-delete-directory (dir entry)
(let ((directory-list (cdr entry))
- (directory (file-cache-canonical-directory dir))
- )
+ (directory (file-cache-canonical-directory dir)))
(and (member directory directory-list)
(if (equal 1 (length directory-list))
(setq file-cache-alist
(delq entry file-cache-alist))
- (setcdr entry (delete directory directory-list)))
- )
- ))
+ (setcdr entry (delete directory directory-list))))))
-(defun file-cache-delete-directory-list (directory-list)
- "Delete DIRECTORY-LIST (a list of directories) from the file cache."
- (interactive "XDirectory List: ")
- (mapcar 'file-cache-delete-directory directory-list))
+(defun file-cache-delete-directory-list (directories)
+ "Delete DIRECTORIES (a list of directory names) from the file cache.
+If called interactively, read the directory names one by one."
+ (interactive (list (file-cache--read-list nil "Uncache")))
+ (dolist (d directories)
+ (file-cache-delete-directory d)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utility functions
file-cache-ignore-case)))
(len (length directory-list))
(directory)
- (num)
- )
+ (num))
(if (not (listp directory-list))
(error "Filecache: unknown type in file-cache-alist for key %s" file))
(cond
;; Multiple elements
(t
(let* ((minibuffer-dir (file-name-directory (minibuffer-contents)))
- (dir-list (member minibuffer-dir directory-list))
- )
+ (dir-list (member minibuffer-dir directory-list)))
(setq directory
;; If the directory is in the list, return the next element
;; Otherwise, return the first element
(or (elt directory-list
(setq num (1+ (- len (length dir-list)))))
(elt directory-list (setq num 0)))
- (elt directory-list (setq num 0))))
- )
- )
- )
+ (elt directory-list (setq num 0)))))))
;; If there were multiple directories, set up a minibuffer message
(setq file-cache-multiple-directory-message
(and num (format " [%d of %d]" (1+ num) len)))
(completion-string (try-completion string file-cache-alist))
(completion-list)
(len)
- (file-cache-string)
- )
+ (file-cache-string))
(cond
;; If it's the only match, replace the original contents
((or arg (eq completion-string t))
(setq file-cache-string (file-cache-file-name string))
(if (string= file-cache-string (minibuffer-contents))
- (file-cache-temp-minibuffer-message file-cache-sole-match-message)
+ (minibuffer-message file-cache-sole-match-message)
(delete-minibuffer-contents)
(insert file-cache-string)
(if file-cache-multiple-directory-message
- (file-cache-temp-minibuffer-message
- file-cache-multiple-directory-message))
- ))
+ (minibuffer-message file-cache-multiple-directory-message))))
;; If it's the longest match, insert it
((stringp completion-string)
(progn
(delete-minibuffer-contents)
(insert (file-cache-file-name completion-string))
- (setq file-cache-last-completion nil)
- )
- (file-cache-temp-minibuffer-message file-cache-non-unique-message)
- (setq file-cache-last-completion string)
- )
+ (setq file-cache-last-completion nil))
+ (minibuffer-message file-cache-non-unique-message)
+ (setq file-cache-last-completion string))
(setq file-cache-last-completion string)
(setq completion-list (all-completions string file-cache-alist)
len (length completion-list))
(substring completion-string (length string)))
;; Add our own setup function to the Completions Buffer
(let ((completion-setup-hook
- (reverse
- (append (list 'file-cache-completion-setup-function)
- completion-setup-hook)))
- )
+ (append completion-setup-hook
+ (list 'file-cache-completion-setup-function))))
(with-output-to-temp-buffer file-cache-completions-buffer
- (display-completion-list completion-list string))
- )
- )
+ (display-completion-list
+ (completion-hilit-commonality completion-list
+ (length string))))))
(setq file-cache-string (file-cache-file-name completion-string))
(if (string= file-cache-string (minibuffer-contents))
- (file-cache-temp-minibuffer-message
- file-cache-sole-match-message)
+ (minibuffer-message file-cache-sole-match-message)
(delete-minibuffer-contents)
(insert file-cache-string)
(if file-cache-multiple-directory-message
- (file-cache-temp-minibuffer-message
- file-cache-multiple-directory-message)))
+ (minibuffer-message file-cache-multiple-directory-message)))
)))
;; No match
((eq completion-string nil)
- (file-cache-temp-minibuffer-message file-cache-no-match-message))
- )
-))
-
-;; Lifted from "complete.el"
-(defun file-cache-temp-minibuffer-message (msg)
- "A Lisp version of `temp_minibuffer_message' from minibuf.c."
- (let ((savemax (point-max)))
- (save-excursion
- (goto-char (point-max))
- (insert msg))
- (let ((inhibit-quit t))
- (sit-for 2)
- (delete-region savemax (point-max))
- (if quit-flag
- (setq quit-flag nil
- unread-command-events (list 7))))))
+ (minibuffer-message file-cache-no-match-message)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Completion functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun file-cache-completion-setup-function ()
- (set-buffer file-cache-completions-buffer)
-
- (if file-cache-completions-keymap
- nil
- (setq file-cache-completions-keymap
- (copy-keymap completion-list-mode-map))
- (define-key file-cache-completions-keymap [mouse-2]
- 'file-cache-mouse-choose-completion)
- (define-key file-cache-completions-keymap "\C-m"
- 'file-cache-choose-completion))
+ (with-current-buffer standard-output ;; i.e. file-cache-completions-buffer
+ (use-local-map file-cache-completions-keymap)))
- (use-local-map file-cache-completions-keymap)
- )
-
-(defun file-cache-choose-completion ()
+(defun file-cache-choose-completion (&optional event)
"Choose a completion in the `*Completions*' buffer."
- (interactive)
+ (interactive (list last-nonmenu-event))
(let ((completion-no-auto-exit t))
- (choose-completion)
+ (choose-completion event)
(select-window (active-minibuffer-window))
- (file-cache-minibuffer-complete nil)
- )
- )
+ (file-cache-minibuffer-complete nil)))
-(defun file-cache-mouse-choose-completion (event)
- "Choose a completion with the mouse."
- (interactive "e")
- (let ((completion-no-auto-exit t))
- (mouse-choose-completion event)
- (select-window (active-minibuffer-window))
- (file-cache-minibuffer-complete nil)
- )
- )
+(define-obsolete-function-alias 'file-cache-mouse-choose-completion
+ 'file-cache-choose-completion "23.2")
(defun file-cache-complete ()
"Complete the word at point, using the filecache."
(interactive)
- (let (start pattern completion all)
+ (let ((start
(save-excursion
(skip-syntax-backward "^\"")
- (setq start (point)))
- (setq pattern (buffer-substring-no-properties start (point)))
- (setq completion (try-completion pattern file-cache-alist))
- (setq all (all-completions pattern file-cache-alist nil))
- (cond ((eq completion t))
- ((null completion)
- (message "Can't find completion for \"%s\"" pattern)
- (ding))
- ((not (string= pattern completion))
- (delete-region start (point))
- (insert completion)
- )
- (t
- (with-output-to-temp-buffer "*Completions*"
- (display-completion-list all pattern))
- ))
- ))
+ (point))))
+ (completion-in-region start (point) file-cache-alist)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Show parts of the cache
"Output a list of files whose names (not including directories)
match REGEXP."
(let ((results))
- (mapc
- (function
- (lambda(cache-element)
- (and (string-match regexp
- (elt cache-element 0))
- (if results
- (nconc results (list (elt cache-element 0)))
- (setq results (list (elt cache-element 0)))))))
- file-cache-alist)
- results))
+ (dolist (cache-element file-cache-alist)
+ (and (string-match regexp (elt cache-element 0))
+ (push (elt cache-element 0) results)))
+ (nreverse results)))
(defun file-cache-files-matching (regexp)
"Output a list of files whose names (not including directories)
"*File Cache Files Matching*")))
(erase-buffer)
(insert
- (mapconcat
- 'identity
- results
- "\n"))
+ (mapconcat #'identity results "\n"))
(goto-char (point-min))
(display-buffer buf)))
(interactive
(list (completing-read "File Cache: " file-cache-alist)))
(message "%s" (assoc-string file file-cache-alist
- file-cache-ignore-case))
- )
+ file-cache-ignore-case)))
(defun file-cache-display ()
"Display the file cache."
(with-current-buffer
(get-buffer-create buf)
(erase-buffer)
- (mapc
- (function
- (lambda(item)
- (insert (nth 1 item) (nth 0 item) "\n")))
- file-cache-alist)
- (pop-to-buffer buf)
- )))
+ (dolist (item file-cache-alist)
+ (insert (nth 1 item) (nth 0 item) "\n"))
+ (pop-to-buffer buf))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Keybindings
(provide 'filecache)
-;;; arch-tag: 433d3ca4-4af2-47ce-b2cf-1f727460f538
;;; filecache.el ends here