Download
(defgroup timid nil
"Timid Completion Group."
:tag "Timid Completion"
:link '(emacs-library-link :tag "Source Lisp File" "timid.el")
:prefix "timid-"
:version "22"
:group 'emacs)
(defcustom timid-enable-globally nil
"*Enable timid for all commands which use the minibuffer.
Note that this is not yet tested with every emacs command. YMMV."
:type 'boolean
:version "22"
:group 'timid)
(defcustom timid-search-delay 0.5
"*Idle time after last input event before searching for matches in the history."
:type 'number
:version "22"
:group 'timid)
(defcustom timid-list-basename-matches-first t
"*In case of file completion put those files first in the
completion list where the basename matches the pattern."
:type 'boolean
:version "22"
:group 'timid)
(defcustom timid-filter-regexps nil
"*List of regular expressions to filter out unwanted files from the
output."
:type '(repeat regexp)
:group 'timid)
(defcustom timid-transform-regexps nil
"*List of (REGEXP . REPLACEMENT) pairs to transform matching file
path names. It's useful when the matching path names are very long and
they have a component which can safely be replaced with a shorter
indicator string.
For example this rule:
(push '(\"^/very/long/path/to/projectx/\" . \"<projx>/\")
timid-transform-regexps)
will display file names under \"projectx\" like this:
<projx>/sources/main.c
<projx>/sources/test.c
"
:type '(repeat (cons regexp regexp))
:version "22"
:group 'timid)
(defcustom timid-use-locate nil
"*Non-nil means use locate.
It uses locate to find further completions not present in the history in case
of file completion."
:type 'boolean
:version "22"
:group 'timid)
(defcustom timid-locate-databases nil
"*Database files string or nil.
If it is string, it has a list of database files separated with colon to be
used by the locate command.
If it is nil, the system default database is used."
:type '(choice (const :tag "Default Database" nil)
(string :tag "Database(s)"))
:version "22"
:group 'timid)
(defcustom timid-locate-minimum-pattern-length 3
"*Minimum number of characters needed to start file searching with locate."
:type 'integer
:version "22"
:group 'timid)
(defcustom timid-locate-matching-filename-limit 500
"*If there are more matching file names than the given limit the
search is terminated automatically. This is useful if a too broad
search input is given and there are hundreds or thousands of matches.
The limit is checked only if `timid-use-locate' is enabled, because
matches from the internal history lists can be processed quickly.
If you don't want to limit the number of matches then set it to nil
instead of a very high number."
:type '(choice integer (const nil))
:group 'timid)
(defcustom timid-keys
(list (cons (kbd "<RET>") 'timid-select-file)
(cons (kbd "<ESC>") 'timid-cleanup)
(cons (kbd "<up>") 'timid-previous-line)
(cons (kbd "<down>") 'timid-next-line)
(cons (kbd "<prior>") 'timid-previous-page)
(cons (kbd "<next>") 'timid-next-page))
"*Alist of keybindings for timid completion."
:type '(alist :key-type (sexp :tag "Key")
:value-type (function :tag "Function"))
:version "22"
:group 'timid)
(dolist (cmd '(forward-char
forward-char-nomark
backward-char
backward-char-nomark
timid-select-file
timid-previous-line
timid-next-line
timid-previous-page
timid-next-page))
(put cmd 'timid-completion 'allowed))
(dolist (cmd '(self-insert-command
delete-backward-char
icicle-self-insert
icicle-delete-backward-char))
(put cmd 'timid-completion 'edit))
(dolist (cmd '(globalff))
(put cmd 'timid-completion 'disabled))
(defvar timid-buffer "*timid*"
"Name of buffer showing completions.")
(defvar timid-saved-commands nil
"List of commands saved when timid overrides keys in the local map.")
(defvar timid-window-config nil
"Window configuration before timid completions were shown.")
(defvar timid-pattern-start-position nil
"Position of beginning of pattern in the minibuffer.")
(defvar timid-overlay nil
"Overlay used to highlight the currently selected file.")
(defvar timid-previous-pattern ""
"The last pattern used to update the completion buffer.")
(defvar timid-trigger-command nil
"The current command which triggered timid completion.")
(defvar timid-history-matches nil
"The list of matches for the current input pattern from the history.")
(defvar timid-locate-process nil
"The current locate process.")
(defvar timid-locate-basename-search nil
"Indicates whether this is the basename search pass of locate.
When searching with locate those files are listed first where the
basename matches the search pattern and then those where the match is
anywhere in the path.")
(defvar timid-locate-pattern nil
"The current search pattern used by locate.")
(defvar timid-mode nil
"*Non-nil means Timid Completion mode is enabled.")
(defun timid-mode (&optional arg)
"Toggle Timid Completion mode.
Non-nil prefix ARG turns mode on if ARG is positive, else turns it off."
(interactive "P")
(setq timid-mode (if arg
(> (prefix-numeric-value arg) 0)
(not timid-mode)))
(if timid-mode
(progn (add-hook 'minibuffer-setup-hook 'timid-minibuffer-setup)
(add-hook 'minibuffer-exit-hook 'timid-minibuffer-exit)
(message "Timid completion mode is enabled."))
(remove-hook 'minibuffer-setup-hook 'timid-minibuffer-setup)
(remove-hook 'minibuffer-exit-hook 'timid-minibuffer-exit)
(message "Timid completion mode is disabled.")))
(defun timid-minibuffer-setup ()
"Prepare timid completion if it is enabled for the current command."
(when (and (not (eq (get this-command 'timid-completion) 'disabled))
(or timid-enable-globally
(eq (get this-command 'timid-completion) 'enabled)))
(setq timid-pattern-start-position (point))
(setq timid-previous-pattern "")
(setq timid-trigger-command this-command)
(add-hook 'pre-command-hook 'timid-pre-command)
(add-hook 'post-command-hook 'timid-post-command)))
(defun timid-minibuffer-exit ()
"Cleanup after timid before exiting the minibuffer."
(timid-cleanup))
(defun timid-pre-command ()
"Terminate timid if a command is used which is not allowed during
timid completion."
(unless (or (eq (get this-command 'timid-completion) 'allowed)
(eq (get this-command 'timid-completion) 'edit))
(timid-cleanup)))
(defun timid-post-command ()
"If a pattern editing command is used during timid completion and
there is no new input event within `timid-search-delay' seconds then
update the list of completions."
(if (eq (get this-command 'timid-completion) 'edit)
(when (sit-for (let ((func (get timid-trigger-command
'timid-search-delay-function)))
(if func
(funcall func)
timid-search-delay)))
(let ((pattern
(or (let ((func (get timid-trigger-command
'timid-pattern-function)))
(if func
(funcall func)))
(if (>= (line-end-position) timid-pattern-start-position)
(buffer-substring timid-pattern-start-position
(line-end-position))
"")))
(history-variable (or (get timid-trigger-command
'timid-candidates-variable)
minibuffer-history-variable)))
(unless (equal pattern timid-previous-pattern)
(setq timid-previous-pattern pattern)
(with-current-buffer (get-buffer-create timid-buffer)
(erase-buffer)
(if timid-overlay
(move-overlay timid-overlay (point-min) (point-min)
(get-buffer timid-buffer))
(setq timid-overlay (make-overlay (point-min) (point-min)
(get-buffer timid-buffer)))
(overlay-put timid-overlay 'face 'highlight))
(timid-set-state nil)
(if (not (equal pattern ""))
(let ((items (symbol-value history-variable))
matches)
(unless (stringp (car items))
(setq items (mapcar (lambda (x) (prin1-to-string x))
items)))
(setq items (reverse items))
(if (eq history-variable 'file-name-history)
(let ((file (buffer-file-name (cadr (buffer-list)))))
(if file
(setq items (append
(directory-files
(file-name-directory file)
t)
items)))))
(let ((transformers
(if (and timid-list-basename-matches-first
(eq history-variable 'file-name-history))
'(file-name-directory file-name-nondirectory)
'(identity))))
(dolist (transformer transformers)
(dolist (item items)
(let ((transformed-item (funcall transformer item)))
(when (and transformed-item
(string-match pattern transformed-item))
(setq matches (delete item matches))
(push item matches))))))
(setq timid-history-matches matches)
(dolist (match matches)
(insert match "\n"))
(when (eq history-variable 'file-name-history)
(goto-char (point-min))
(timid-transform-file-names)))))
(if (or (= (with-current-buffer (get-buffer timid-buffer)
(buffer-size))
0)
(with-current-buffer (get-buffer timid-buffer)
(goto-char (point-min))
(and (equal (buffer-substring (line-beginning-position)
(line-end-position))
pattern)
(forward-line 1)
(eobp))))
(timid-hide-completion-window)
(timid-show-completion-window)
(save-selected-window
(select-window (get-buffer-window timid-buffer))
(goto-char (point-min))
(timid-mark-current-line)))
(when (and timid-use-locate
(eq history-variable 'file-name-history)
(>= (length pattern)
timid-locate-minimum-pattern-length))
(setq timid-locate-basename-search timid-list-basename-matches-first)
(setq timid-locate-pattern pattern)
(timid-locate pattern)))))))
(defun timid-mark-current-line ()
"Mark current line as selected."
(move-overlay timid-overlay
(line-beginning-position)
(1+ (line-end-position))))
(defun timid-previous-line ()
"Move selection to the previous line."
(interactive)
(timid-move-selection 'next-line -1))
(defun timid-next-line ()
"Move selection to the next line."
(interactive)
(timid-move-selection 'next-line 1))
(defun timid-previous-page ()
"Move selection back with a pageful."
(interactive)
(timid-move-selection 'scroll-down nil))
(defun timid-next-page ()
"Move selection forward with a pageful."
(interactive)
(timid-move-selection 'scroll-up nil))
(defun timid-move-selection (movefunc movearg)
"Move the selection marker to a new position determined by
MOVEFUNC and MOVEARG."
(save-selected-window
(select-window (get-buffer-window timid-buffer))
(condition-case nil
(funcall movefunc movearg)
(beginning-of-buffer (goto-char (point-min)))
(end-of-buffer (goto-char (point-max))))
(if (eobp)
(next-line -1))
(timid-mark-current-line)))
(defun timid-select-file ()
"Open the selected file."
(interactive)
(let ((selected (with-current-buffer timid-buffer
(or (get-text-property (overlay-start timid-overlay)
'timid-orig-filename)
(buffer-substring-no-properties
(overlay-start timid-overlay)
(line-end-position)))))
(func (get timid-trigger-command 'timid-visit-file-function)))
(if func
(funcall func selected)
(beginning-of-line)
(kill-line)
(insert selected)
(exit-minibuffer))))
(defun timid-cleanup ()
"Hide the timid completion window if necessary and remove the
installed hooks."
(interactive)
(timid-hide-completion-window)
(remove-hook 'pre-command-hook 'timid-pre-command)
(remove-hook 'post-command-hook 'timid-post-command))
(defun timid-show-completion-window ()
"Show the completion window and rebind keys for file selection if
necessary."
(unless timid-window-config
(setq timid-window-config (current-window-configuration))
(save-selected-window
(pop-to-buffer timid-buffer)
(setq mode-name "Timid")
(setq cursor-type nil))
(dolist (key timid-keys)
(timid-redefine-key (car key) (cdr key)))))
(defun timid-hide-completion-window ()
"Hide the completion window and restore bindings for the keys used
for file selection if necessary. Also kill the locate process if it is
running."
(when timid-window-config
(set-window-configuration timid-window-config)
(setq timid-window-config nil)
(with-current-buffer timid-buffer
(setq cursor-type t))
(dolist (def timid-saved-commands)
(define-key (current-local-map) (car def) (cdr def)))
(setq timid-saved-commands nil)
(timid-kill-locate-process)))
(defun timid-redefine-key (key command)
"Redefine KEY to COMMAND and save the previous binding to
`timid-saved-commands'."
(push (cons key (lookup-key (current-local-map) key))
timid-saved-commands)
(define-key (current-local-map) key command))
(defun timid-set-state (state)
"Set STATE in mode line."
(with-current-buffer timid-buffer
(setq mode-line-process (if state (concat ":" state)))
(force-mode-line-update)))
(defun timid-transform-file-names ()
"Transform file names in the timid buffer from the current line to
the end of *timid* buffer according to `timid-transform-regexps'."
(let ((begin (line-beginning-position)))
(dolist (rule timid-transform-regexps)
(goto-char begin)
(while (re-search-forward (car rule) nil t)
(let ((orig-path
(or (get-text-property (line-beginning-position)
'timid-orig-filename)
(buffer-substring-no-properties
(line-beginning-position) (line-end-position)))))
(replace-match (cdr rule))
(put-text-property (line-beginning-position) (line-end-position)
'timid-orig-filename
orig-path))))))
(defun timid-locate (pattern)
"Start a locate process for query PATTERN."
(timid-kill-locate-process)
(setq timid-locate-process (apply 'start-process "timid-process" nil
"locate"
(append
(if timid-locate-databases
(list (concat
"--database="
timid-locate-databases)))
(if timid-locate-basename-search
(list "-b"))
(list "-i"
"-r"
pattern))))
(set-process-filter timid-locate-process 'timid-locate-output-filter)
(set-process-sentinel timid-locate-process 'timid-locate-process-sentinel)
(timid-set-state "locating"))
(defun timid-locate-output-filter (process string)
"Process output from locate."
(with-current-buffer (get-buffer-create timid-buffer)
(save-excursion
(goto-char (point-max))
(let (line)
(save-excursion
(insert string)
(setq line (buffer-substring (line-beginning-position)
(line-end-position)))
(delete-region (line-beginning-position) (line-end-position)))
(dolist (regexp timid-filter-regexps)
(flush-lines regexp))
(save-excursion
(beginning-of-line)
(let (start file)
(while (not (eobp))
(setq file (buffer-substring (line-beginning-position)
(line-end-position)))
(if (member file timid-history-matches)
(setq start (line-beginning-position))
(push file timid-history-matches)
(setq start nil))
(forward-line)
(if start
(delete-region start (point))))))
(timid-transform-file-names)
(goto-char (point-max))
(insert line))))
(when (= (overlay-start timid-overlay)
(overlay-end timid-overlay))
(timid-show-completion-window)
(save-selected-window
(select-window (get-buffer-window timid-buffer))
(timid-mark-current-line)))
(when (and timid-locate-matching-filename-limit
(>= (length timid-history-matches)
timid-locate-matching-filename-limit))
(timid-kill-locate-process)
(timid-set-state "limit reached")))
(defun timid-locate-process-sentinel (process event)
"Set status to finished if the locate process exits."
(unless (eq 'run (process-status process))
(if timid-locate-basename-search
(progn (setq timid-locate-basename-search nil)
(timid-locate timid-locate-pattern))
(timid-set-state "finished"))))
(defun timid-kill-locate-process ()
"Kill locate process."
(when timid-locate-process
(set-process-filter timid-locate-process nil)
(set-process-sentinel timid-locate-process nil)
(delete-process timid-locate-process)
(setq timid-locate-process nil)))
(put 'iswitchb-buffer 'timid-completion 'disabled)
(defvar timid-iswitchb-selected-file nil
"The file which was selected using timid from iswitchb.")
(defun timid-iswitchb-setup ()
"Setup timid to work with iswitchb."
(interactive)
(require 'iswitchb)
(put 'iswitchb-buffer 'timid-pattern-function 'timid-iswitchb-get-pattern)
(put 'iswitchb-buffer 'timid-candidates-variable 'file-name-history)
(put 'iswitchb-buffer 'timid-visit-file-function 'timid-iswitchb-visit-file)
(put 'iswitchb-buffer 'timid-search-delay-function 'timid-iswitchb-search-delay)
(put 'iswitchb-buffer 'timid-completion 'enabled))
(defun timid-iswitchb-get-pattern ()
"Return the current iswitch pattern."
iswitchb-text)
(defun timid-iswitchb-search-delay ()
"If there are matching buffers use a longer completion delay to
interfere less with normal buffer switching. Otherwise, use the
standard delay."
(if iswitchb-matches
(* 2 timid-search-delay)
timid-search-delay))
(defun timid-iswitchb-visit-file (file)
"Visit file from iswitchb."
(setq timid-iswitchb-selected-file file)
(add-hook 'minibuffer-setup-hook 'timid-iswitchb-visit-file-hook)
(iswitchb-find-file))
(defun timid-iswitchb-visit-file-hook ()
"Visit file selected from iswitchb."
(remove-hook 'minibuffer-setup-hook 'timid-iswitchb-visit-file-hook)
(delete-region (line-beginning-position) (line-end-position))
(insert timid-iswitchb-selected-file)
(setq unread-command-events (cons ?\n unread-command-events)))
(provide 'timid)