(eval-when-compile
(require 'find-lisp))
(defgroup file-cache nil
"Find files using a pre-loaded cache."
:group 'files
:group 'convenience
:prefix "file-cache-")
(defcustom file-cache-filter-regexps
(list "~$" "\\.o$" "\\.exe$" "\\.a$" "\\.elc$" ",v$" "\\.output$"
"\\.$" "#$" "\\.class$")
"*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."
:type '(repeat regexp)
:group 'file-cache)
(defcustom file-cache-find-command "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.
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'.
Under Windows operating system where Cygwin is available, this value
should be t."
:type '(choice (const :tag "Yes" t)
(const :tag "No" nil)
(const :tag "Unknown" not-defined))
:group 'file-cache)
(defcustom file-cache-locate-command "locate"
"*External program used by `file-cache-add-directory-using-locate'."
:type 'string
:group 'file-cache)
(defcustom file-cache-no-match-message " [File Cache: No match]"
"Message to display when there is no completion."
:type 'string
:group 'file-cache)
(defcustom file-cache-sole-match-message " [File Cache: sole completion]"
"Message to display when there is only one completion."
:type 'string
:group 'file-cache)
(defcustom file-cache-non-unique-message
" [File Cache: complete but not unique]"
"Message to display when there is a non-unique completion."
:type 'string
:group 'file-cache)
(defcustom file-cache-completion-ignore-case
(if (memq system-type (list '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
)
(defcustom file-cache-case-fold-search
(if (memq system-type (list '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
)
(defcustom file-cache-ignore-case
(memq system-type (list '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
)
(defvar file-cache-multiple-directory-message nil)
(defcustom file-cache-completions-buffer "*Completions*"
"Buffer to display completions when using the file cache."
:type 'string
:group 'file-cache)
(defcustom file-cache-buffer "*File Cache*"
"Buffer to hold the cache of file names."
:type 'string
:group 'file-cache)
(defcustom file-cache-buffer-default-regexp "^.+$"
"Regexp to match files in `file-cache-buffer'."
:type 'regexp
:group 'file-cache)
(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
"Keymap for file cache completions buffer.")
(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: ")
(if (not (file-accessible-directory-p directory))
(message "Directory %s does not exist" directory)
(let* ((dir (expand-file-name directory))
(dir-files (directory-files dir t regexp))
)
(mapcar
'(lambda (file)
(if (file-directory-p file)
(setq dir-files (delq file dir-files))
(mapcar
'(lambda (regexp)
(if (string-match regexp file)
(setq dir-files (delq file dir-files))))
file-cache-filter-regexps)))
dir-files)
(file-cache-add-file-list dir-files))))
(defun file-cache-add-directory-list (directory-list &optional regexp)
"Add DIRECTORY-LIST (a list of directory names) to the file cache.
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))
(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))
)
(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)))
)
(setq file-cache-alist
(cons (cons file-name (list dir-name))
file-cache-alist)))
)))
(defun file-cache-add-directory-using-find (directory)
"Use the `find' command to add files to the file cache.
Find is run in DIRECTORY."
(interactive "DAdd files under directory: ")
(let ((dir (expand-file-name directory)))
(when (memq system-type '(windows-nt cygwin))
(if (eq file-cache-find-command-posix-flag 'not-defined)
(setq file-cache-find-command-posix-flag
(executable-command-find-posix-p file-cache-find-command))))
(set-buffer (get-buffer-create file-cache-buffer))
(erase-buffer)
(call-process file-cache-find-command nil
(get-buffer file-cache-buffer) nil
dir "-name"
(if (memq system-type '(windows-nt cygwin))
(if file-cache-find-command-posix-flag
"\\*"
"'*'")
"*")
"-print")
(file-cache-add-from-file-cache-buffer)))
(defun file-cache-add-directory-using-locate (string)
"Use the `locate' command to add files to the file cache.
STRING is passed as an argument to the locate command."
(interactive "sAdd files using locate string: ")
(set-buffer (get-buffer-create file-cache-buffer))
(erase-buffer)
(call-process file-cache-locate-command nil
(get-buffer file-cache-buffer) nil
string)
(file-cache-add-from-file-cache-buffer))
(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
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 "DAdd directory: ")
(require 'find-lisp)
(mapcar
(function
(lambda(file)
(or (file-directory-p file)
(let (filtered)
(mapcar
(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 "^"))))
(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)
(mapcar
(function (lambda (elt)
(goto-char (point-min))
(delete-matching-lines elt)))
file-cache-filter-regexps)
(goto-char (point-min))
(let ((full-filename))
(while (re-search-forward
(or regexp file-cache-buffer-default-regexp)
(point-max) t)
(setq full-filename (buffer-substring-no-properties
(match-beginning 0) (match-end 0)))
(file-cache-add-file full-filename))))
(defun file-cache-clear-cache ()
"Clear the file cache."
(interactive)
(setq file-cache-alist nil))
(defun file-cache-delete-file (file)
"Delete FILE from the file cache."
(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))
(defun file-cache-delete-file-regexp (regexp)
"Delete files matching REGEXP from the file cache."
(interactive "sRegexp: ")
(let ((delete-list))
(mapcar '(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))))
(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))
(mapcar
'(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))))
(defun file-cache-do-delete-directory (dir entry)
(let ((directory-list (cdr entry))
(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)))
)
))
(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-directory-name (file)
(let* ((directory-list (cdr (assoc-string
file file-cache-alist
file-cache-ignore-case)))
(len (length directory-list))
(directory)
(num)
)
(if (not (listp directory-list))
(error "Filecache: unknown type in file-cache-alist for key %s" file))
(cond
((eq 1 len)
(setq directory (elt directory-list 0)))
((eq 0 len)
(error "Filecache: no directory found for key %s" file))
(t
(let* ((minibuffer-dir (file-name-directory (minibuffer-contents)))
(dir-list (member minibuffer-dir directory-list))
)
(setq directory
(if dir-list
(or (elt directory-list
(setq num (1+ (- len (length dir-list)))))
(elt directory-list (setq num 0)))
(elt directory-list (setq num 0))))
)
)
)
(setq file-cache-multiple-directory-message
(and num (format " [%d of %d]" (1+ num) len)))
directory))
(defun file-cache-file-name (file)
(let ((directory (file-cache-directory-name file)))
(concat directory file)))
(defun file-cache-canonical-directory (dir)
(let ((directory dir))
(if (not (char-equal ?/ (string-to-char (substring directory -1))))
(concat directory "/")
directory)))
(defun file-cache-minibuffer-complete (arg)
"Complete a filename in the minibuffer using a preloaded cache.
Filecache does two kinds of substitution: it completes on names in
the cache, and, once it has found a unique name, it cycles through
the directories that the name is available in. With a prefix argument,
the name is considered already unique; only the second substitution
\(directories) is done."
(interactive "P")
(let*
(
(completion-ignore-case file-cache-completion-ignore-case)
(case-fold-search file-cache-case-fold-search)
(string (file-name-nondirectory (minibuffer-contents)))
(completion-string (try-completion string file-cache-alist))
(completion-list)
(len)
(file-cache-string)
)
(cond
((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)
(delete-minibuffer-contents)
(insert file-cache-string)
(if file-cache-multiple-directory-message
(file-cache-temp-minibuffer-message
file-cache-multiple-directory-message))
))
((stringp completion-string)
(if (and (string= string completion-string)
(assoc-string string file-cache-alist
file-cache-ignore-case))
(if (and (eq last-command this-command)
(string= file-cache-last-completion 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 string)
(setq completion-list (all-completions string file-cache-alist)
len (length completion-list))
(if (> len 1)
(progn
(goto-char (point-max))
(insert
(substring completion-string (length string)))
(let ((completion-setup-hook
(reverse
(append (list 'file-cache-completion-setup-function)
completion-setup-hook)))
)
(with-output-to-temp-buffer file-cache-completions-buffer
(display-completion-list completion-list 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)
(delete-minibuffer-contents)
(insert file-cache-string)
(if file-cache-multiple-directory-message
(file-cache-temp-minibuffer-message
file-cache-multiple-directory-message)))
)))
((eq completion-string nil)
(file-cache-temp-minibuffer-message file-cache-no-match-message))
)
))
(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))))))
(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))
(use-local-map file-cache-completions-keymap)
)
(defun file-cache-choose-completion ()
"Choose a completion in the `*Completions*' buffer."
(interactive)
(let ((completion-no-auto-exit t))
(choose-completion)
(select-window (active-minibuffer-window))
(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)
)
)
(defun file-cache-complete ()
"Complete the word at point, using the filecache."
(interactive)
(let (start pattern completion all)
(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))
))
))
(defun file-cache-files-matching-internal (regexp)
"Output a list of files whose names (not including directories)
match REGEXP."
(let ((results))
(mapcar
(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))
(defun file-cache-files-matching (regexp)
"Output a list of files whose names (not including directories)
match REGEXP."
(interactive "sFind files matching regexp: ")
(let ((results
(file-cache-files-matching-internal regexp))
buf)
(set-buffer
(setq buf (get-buffer-create
"*File Cache Files Matching*")))
(erase-buffer)
(insert
(mapconcat
'identity
results
"\n"))
(goto-char (point-min))
(display-buffer buf)))
(defun file-cache-debug-read-from-minibuffer (file)
"Debugging function."
(interactive
(list (completing-read "File Cache: " file-cache-alist)))
(message "%s" (assoc-string file file-cache-alist
file-cache-ignore-case))
)
(defun file-cache-display ()
"Display the file cache."
(interactive)
(let ((buf "*File Cache Contents*"))
(with-current-buffer
(get-buffer-create buf)
(erase-buffer)
(mapcar
(function
(lambda(item)
(insert (nth 1 item) (nth 0 item) "\n")))
file-cache-alist)
(pop-to-buffer buf)
)))
(provide 'filecache)