New release.

Changes:

- Added option (globalff-filter-regexps) to filter out unwanted files
from the output
- Added pgup/pgdown bindings for list navigation.



;;; globalff.el --- Global find file

;; Copyright (C) 2006  Free Software Foundation, Inc.

;; This file 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 2, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; 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., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; Start with M-x globallff and type in any substring of any path on
;; your system to display the matching files. The displayed list is
;; updated dynamically as you type more characters or delete some.
;;
;; Needs an up-to-date locate database for file name searching.
;;
;; Since the search is based on locate you can use any globbing
;; characters allowed by the locate command.
;;
;; You can move up/down the list with the cursor keys (I know these
;; bindings are not very Emacsian, but I happen to like them) and
;; select a file to open with Enter.
;;
;; You can quit with C-g.
;;
;; See the variable `globalff-map' for further bindings.
;;

;;; Code:

;;
;; User configurable variables
;;

(defvar globalff-case-sensitive-search nil
  "Whether to use case sensitive pattern matching.")

(defvar globalff-regexp-search nil
  "Whether to use regular expression pattern matching.")

(defvar globalff-databases nil
  "List of database files separated with colon to be used by the locate
command.
If nil then the system default database is used.")

(defvar globalff-filter-regexps nil
  "List of regular expressions to filter out unwanted files from the
output.")

(defvar globalff-minimum-input-length 3
  "The minimum number of characters needed to start file searching.")

(defvar globalff-search-delay 0.5
  "Idle time after last input event, before starting the search.")

(defvar globalff-map
  (let ((map (copy-keymap minibuffer-local-map)))
    (define-key map (kbd "<down>") 'globalff-next-line)
    (define-key map (kbd "<up>") 'globalff-previous-line)
    (define-key map (kbd "<prior>") 'globalff-previous-page)
    (define-key map (kbd "<next>") 'globalff-next-page)
    (define-key map (kbd "C-c") 'globalff-toggle-case-sensitive-search)
    ;; I wanted to choose C-t as a homage to iswitchb, but
    ;; transpose-chars can be useful during pattern editing
    (define-key map (kbd "C-r") 'globalff-toggle-regexp-search)
    (define-key map (kbd "C-s") 'globalff-toggle-around-globs)
    map)
  "Keymap for globalff.")

;;
;; End of user configurable variables
;;

(defvar globalff-idle-timer nil
  "Idle timer for monitoring typed characters.")

(defconst globalff-buffer "*globalff*"
  "Buffer used for finding files.")

(defvar globalff-previous-input ""
  "The previous input substring used for searching.")

(defvar globalff-overlay nil
  "Overlay used to highlight the current selection.")


(defun globalff-output-filter (process string)
  "Avoid moving of point if the buffer is empty."
  (with-current-buffer globalff-buffer
    (let* ((empty (= (buffer-size) 0))
           (moving (and (not empty)
                        (= (point) (process-mark process)))))
      (save-excursion
        ;; Insert the text, advancing the process marker.
        (goto-char (process-mark process))

        (let ((begin (line-beginning-position)))
          (insert string)

          ;; filter out unwanted lines
          (if globalff-filter-regexps
              ;; current line can be incomplete, so store and remove
              ;; it before filtering
              (let ((line (buffer-substring (line-beginning-position)
                                            (line-end-position))))
                (delete-region (line-beginning-position)
(line-end-position))

                (dolist (regexp globalff-filter-regexps)
                  (goto-char begin)
                  (flush-lines regexp))

                (goto-char (point-max))
                (insert line)))

          (set-marker (process-mark process) (point))))

      (if empty
          (globalff-mark-current-line))

      (if moving (goto-char (process-mark process))))))


(defun globalff-mark-current-line ()
  "Mark current line with a distinctive color."
  (move-overlay globalff-overlay (point-at-bol) (point-at-eol)))


(defun globalff-previous-line ()
  "Move selection to the previous line."
  (interactive)
  (globalff-move-selection 'next-line -1))


(defun globalff-next-line ()
  "Move selection to the next line."
  (interactive)
  (globalff-move-selection 'next-line 1))


(defun globalff-previous-page ()
  "Move selection back with a pageful."
  (interactive)
  (globalff-move-selection 'scroll-down nil))


(defun globalff-next-page ()
  "Move selection forward with a pageful."
  (interactive)
  (globalff-move-selection 'scroll-up nil))


(defun globalff-move-selection (movefunc movearg)
  "Move the selection marker to a new position determined by
MOVEFUNC and MOVEARG."
  (unless (= (buffer-size (get-buffer globalff-buffer)) 0)
    (let ((old-window (selected-window)))
      (select-window (get-buffer-window globalff-buffer))

      (condition-case nil
          (funcall movefunc movearg)
        (beginning-of-buffer (goto-char (point-min)))
        (end-of-buffer (goto-char (point-max))))

      ;; if line end is point-max then it's either an incomplete line
or
      ;; the end of the output, so move up a line
      (if (= (line-end-position) (point-max))
          (next-line -1))

      (globalff-mark-current-line)

      (select-window old-window))))


(defun globalff-process-sentinel (process event)
  "Prevent printing of process status messages into the output buffer."
  (if (eq nil (process-status globalff-buffer))
      (globalff-set-state "finished")))


(defun globalff-check-input ()
  "Check input string and start/stop search if necessary."
  (unless (equal (minibuffer-contents) globalff-previous-input)
    (globalff-restart-search)))


(defun globalff-restart-search ()
  "Stop the current search if any and start a new one if needed."
  (let ((input (minibuffer-contents)))
    (setq globalff-previous-input input)

    (globalff-kill-process)
    (with-current-buffer globalff-buffer
      (erase-buffer))
    (globalff-set-state "idle")

    (unless (or (equal input "")
                (< (length input) globalff-minimum-input-length))
      (let ((process (apply 'start-process "globalff-process"
globalff-buffer
                            "locate"
                            (append
                             (unless globalff-case-sensitive-search
                               (list "-i"))

                             (if globalff-regexp-search
                               (list "-r"))

                             (when globalff-databases
                               (list (concat "--database="
                                             globalff-databases)))

                             (list input)))))
        (globalff-set-state "searching")
        (set-process-filter process 'globalff-output-filter)
        (set-process-sentinel process 'globalff-process-sentinel)))))


(defun globalff-kill-process ()
  "Kill find process."
  (if (eq 'run (process-status globalff-buffer))
      (delete-process globalff-buffer)))


(defun globalff-set-state (state)
  "Set STATE in mode line."
  (with-current-buffer globalff-buffer
    (setq mode-line-process (concat ":" (if
globalff-case-sensitive-search
                                            "case"
                                          "nocase")
                                    "/"  (if globalff-regexp-search
                                            "regexp"
                                           "glob")
                                    ":" state))
    (force-mode-line-update)))


(defun globalff-toggle-case-sensitive-search ()
  "Toggle state of case sensitive pattern matching."
  (interactive)
  (setq globalff-case-sensitive-search (not
globalff-case-sensitive-search))
  (globalff-restart-search))


(defun globalff-toggle-regexp-search ()
  "Toggle state of regular expression pattern matching."
  (interactive)
  (setq globalff-regexp-search (not globalff-regexp-search))
  (globalff-restart-search))


(defun globalff-toggle-around-globs ()
  "Put/remove asterisks around pattern if glob matching is used. This
make it easier to use globs, since by default glob patterns have to
match the file name exactly."
  (interactive)
  (unless globalff-regexp-search
    (let* ((pattern (minibuffer-contents))
           (len (length pattern)))
      (if (> len 2)
          (save-excursion
            (if  (and (= (aref pattern 0) ?*)
                      (= (aref pattern (1- len)) ?*))
                ;; remove asterisks from around pattern
                (progn
                  (beginning-of-line)
                  (delete-char 1)
                  (end-of-line)
                  (delete-char -1))

                ;; put asterisks around pattern
              (beginning-of-line)
              (insert "*")
              (end-of-line)
              (insert "*")))))))


(defun globalff ()
  "Start global find file."
  (interactive)
  (let ((winconfig (current-window-configuration)))
    (pop-to-buffer globalff-buffer)
    (erase-buffer)
    (setq mode-name "GlobalFF")

    (unless globalff-overlay
      (setq globalff-overlay (make-overlay 0 0))
      (overlay-put globalff-overlay 'face 'region))

    (globalff-set-state "idle")
    (setq globalff-previous-input "")
    (setq globalff-idle-timer
          (run-with-idle-timer globalff-search-delay t
'globalff-check-input))

    (with-current-buffer globalff-buffer
      (setq cursor-type nil))

    (unwind-protect
        (let ((minibuffer-local-map globalff-map))
          (read-string "substring: "))

      (globalff-kill-process)
      (cancel-timer globalff-idle-timer)

      (with-current-buffer globalff-buffer
        (setq cursor-type t))

      (set-window-configuration winconfig)))

  (unless (= (buffer-size (get-buffer globalff-buffer)) 0)
    (find-file
     (with-current-buffer globalff-buffer
       (buffer-substring-no-properties (overlay-start globalff-overlay)
                                       (overlay-end
globalff-overlay))))))
 
 
(provide 'globalff)
;;; globalff.el ends here

_______________________________________________
gnu-emacs-sources mailing list
gnu-emacs-sources@gnu.org
http://lists.gnu.org/mailman/listinfo/gnu-emacs-sources

Reply via email to