;;; imenu.el --- Framework for mode-specific buffer indexes.
-;; Copyright (C) 1994, 1995 Free Software Foundation, Inc.
+;; Copyright (C) 1994, 1995, 1996, 1997 Free Software Foundation, Inc.
;; Author: Ake Stenhoff <etxaksf@aom.ericsson.se>
;; Lars Lindberg <lli@sypro.cap.se>
;;; Customizable variables
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-(defvar imenu-use-keymap-menu nil
- "*Non-nil means use a keymap when making the mouse menu.")
-(defvar imenu-auto-rescan nil
- "*Non-nil means Imenu should always rescan the buffers.")
+(defgroup imenu nil
+ "Mode-specific buffer indexes."
+ :group 'matching
+ :group 'frames)
-(defvar imenu-auto-rescan-maxout 60000
- "* auto-rescan is disabled in buffers larger than this.
-This variable is buffer-local.")
+(defcustom imenu-use-markers t
+ "*Non-nil means use markers instead of integers for Imenu buffer positions.
+Setting this to nil makes Imenu work faster.
-(defvar imenu-always-use-completion-buffer-p nil
+This might not yet be honored by all index-building functions."
+ :type 'boolean
+ :group 'imenu)
+
+
+(defcustom imenu-max-item-length 60
+ "*If a number, truncate Imenu entries to that length."
+ :type 'integer
+ :group 'imenu)
+
+(defcustom imenu-auto-rescan nil
+ "*Non-nil means Imenu should always rescan the buffers."
+ :type 'boolean
+ :group 'imenu)
+
+(defcustom imenu-auto-rescan-maxout 60000
+ "*Imenu auto-rescan is disabled in buffers larger than this size.
+This variable is buffer-local."
+ :type 'integer
+ :group 'imenu)
+
+(defcustom imenu-always-use-completion-buffer-p nil
"*Set this to non-nil for displaying the index in a completion buffer.
-Non-nil means always display the index in a completion buffer.
-Nil means display the index as a mouse menu when the mouse was
-used to invoke `imenu'.
-`never' means never automatically display a listing of any kind.")
+`never' means never automatically display a listing of any kind.
+A value of nil (the default) means display the index as a mouse menu
+if the mouse was used to invoke `imenu'.
+Another non-nil value means always display the index in a completion buffer."
+ :type '(choice (const :tag "On Mouse" nil)
+ (const :tag "Never" never)
+ (sexp :tag "Always" :format "%t\n" t))
+ :group 'imenu)
-(defvar imenu-sort-function nil
+(defcustom imenu-sort-function nil
"*The function to use for sorting the index mouse-menu.
Affects only the mouse index menu.
The function should take two arguments and return T if the first
element should come before the second. The arguments are cons cells;
-\(NAME . POSITION). Look at `imenu--sort-by-name' for an example.")
+\(NAME . POSITION). Look at `imenu--sort-by-name' for an example."
+ :type 'function
+ :group 'imenu)
-(defvar imenu-max-items 25
- "*Maximum number of elements in an mouse menu for Imenu.")
+(defcustom imenu-max-items 25
+ "*Maximum number of elements in a mouse menu for Imenu."
+ :type 'integer
+ :group 'imenu)
-(defvar imenu-scanning-message "Scanning buffer for index (%3d%%)"
+(defcustom imenu-scanning-message "Scanning buffer for index (%3d%%)"
"*Progress message during the index scanning of the buffer.
-If non-nil, user gets a message during the scanning of the buffer
+If non-nil, user gets a message during the scanning of the buffer.
Relevant only if the mode-specific function that creates the buffer
-index use `imenu-progress-message'.")
+index use `imenu-progress-message'."
+ :type 'string
+ :group 'imenu)
-(defvar imenu-space-replacement "^"
+(defcustom imenu-space-replacement "^"
"*The replacement string for spaces in index names.
Used when presenting the index in a completion-buffer to make the
-names work as tokens.")
+names work as tokens."
+ :type 'string
+ :group 'imenu)
-(defvar imenu-level-separator ":"
+(defcustom imenu-level-separator ":"
"*The separator between index names of different levels.
Used for making mouse-menu titles and for flattening nested indexes
-with name concatenation.")
+with name concatenation."
+ :type 'string
+ :group 'imenu)
;;;###autoload
(defvar imenu-generic-expression nil
"The regex pattern to use for creating a buffer index.
-If non-nil this pattern is passed to `imenu-create-index-with-pattern'
+If non-nil this pattern is passed to `imenu--generic-function'
to create a buffer index.
-It is an alist with elements that look like this: (MENU-TITLE
-REGEXP INDEX).
+The value should be an alist with elements that look like this:
+ (MENU-TITLE REGEXP INDEX)
+or like this:
+ (MENU-TITLE REGEXP INDEX FUNCTION ARGUMENTS...)
+with zero or more ARGUMENTS. The former format creates a simple element in
+the index alist when it matches; the latter creates a special element
+of the form (NAME FUNCTION NAME POSITION-MARKER ARGUMENTS...)
+with FUNCTION and ARGUMENTS beiong copied from `imenu-generic-expression'.
MENU-TITLE is a string used as the title for the submenu or nil if the
entries are not nested.
The variable is buffer-local.")
+;;;###autoload
(make-variable-buffer-local 'imenu-generic-expression)
;;;; Hooks
"The function to use for creating a buffer index.
It should be a function that takes no arguments and returns an index
-of the current buffer as an alist. The elements in the alist look
-like: (INDEX-NAME . INDEX-POSITION). You may also nest index list like
-\(INDEX-NAME . INDEX-ALIST).
+of the current buffer as an alist.
+
+Simple elements in the alist look like (INDEX-NAME . INDEX-POSITION).
+Special elements look like (INDEX-NAME FUNCTION ARGUMENTS...).
+A nested sub-alist element looks like (INDEX-NAME SUB-ALIST).
+The function `imenu--subalist-p' tests an element and returns t
+ if it is a sub-alist.
This function is called within a `save-excursion'.
`imenu-prev-index-position-function'.")
(make-variable-buffer-local 'imenu-extract-index-name-function)
+(defvar imenu-default-goto-function 'imenu-default-goto-function
+ "The default function called when selecting an Imenu item.
+The function in this variable is called when selecting a normal index-item.")
+(make-variable-buffer-local 'imenu-default-goto-function)
+
+
+(defun imenu--subalist-p (item)
+ (and (consp (cdr item)) (listp (cadr item))
+ (not (eq (caadr item) 'lambda))))
+
;;;
;;; Macro to display a progress message.
;;; RELPOS is the relative position to display.
(defun imenu-example--name-and-position ()
(save-excursion
(forward-sexp -1)
- (let ((beg (point))
- (end (progn (forward-sexp) (point)))
- (marker (make-marker)))
- (set-marker marker beg)
+ ;; [ydi] modified for imenu-use-markers
+ (let ((beg (if imenu-use-markers (point-marker) (point)))
+ (end (progn (forward-sexp) (point))))
(cons (buffer-substring beg end)
- marker))))
+ beg))))
;;;
;;; Lisp
;;; Split the alist MENULIST into a nested alist, if it is long enough.
;;; In any case, add TITLE to the front of the alist.
(defun imenu--split-menu (menulist title)
- (if (> (length menulist) imenu-max-items)
- (let ((count 0))
- (cons title
- (mapcar
- (function
- (lambda (menu)
- (cons (format "(%s-%d)" title (setq count (1+ count)))
- menu)))
- (imenu--split menulist imenu-max-items))))
- (cons title menulist)))
+ (let (keep-at-top tail)
+ (if (memq imenu--rescan-item menulist)
+ (setq keep-at-top (cons imenu--rescan-item nil)
+ menulist (delq imenu--rescan-item menulist)))
+ (setq tail menulist)
+ (while tail
+ (if (imenu--subalist-p (car tail))
+ (setq keep-at-top (cons (car tail) keep-at-top)
+ menulist (delq (car tail) menulist)))
+ (setq tail (cdr tail)))
+ (if imenu-sort-function
+ (setq menulist
+ (sort
+ (let ((res nil)
+ (oldlist menulist))
+ ;; Copy list method from the cl package `copy-list'
+ (while (consp oldlist) (push (pop oldlist) res))
+ (prog1 (nreverse res) (setcdr res oldlist)))
+ imenu-sort-function)))
+ (if (> (length menulist) imenu-max-items)
+ (let ((count 0))
+ (setq menulist
+ (mapcar
+ (function
+ (lambda (menu)
+ (cons (format "From: %s" (caar menu)) menu)))
+ (imenu--split menulist imenu-max-items)))))
+ (cons title
+ (nconc (nreverse keep-at-top) menulist))))
;;; Split up each long alist that are nested within ALIST
;;; into nested alists.
elt)))
alist))
-;;;
-;;; Find all items in this buffer that should be in the index.
-;;; Returns an alist on the form
-;;; ((NAME . POSITION) (NAME . POSITION) ...)
-;;;
+;;; Truncate all strings in MENULIST to imenu-max-item-length
+(defun imenu--truncate-items (menulist)
+ (mapcar (function
+ (lambda (item)
+ (cond
+ ((consp (cdr item))
+ (imenu--truncate-items (cdr item)))
+ (t
+ ;; truncate if necessary
+ (if (and (numberp imenu-max-item-length)
+ (> (length (car item)) imenu-max-item-length))
+ (setcar item (substring (car item) 0 imenu-max-item-length)))))))
+ menulist))
+
(defun imenu--make-index-alist (&optional noerror)
- ;; Create a list for this buffer only when needed.
+ "Create an index-alist for the definitions in the current buffer.
+
+Simple elements in the alist look like (INDEX-NAME . INDEX-POSITION).
+Special elements look like (INDEX-NAME FUNCTION ARGUMENTS...).
+A nested sub-alist element looks like (INDEX-NAME SUB-ALIST).
+The function `imenu--subalist-p' tests an element and returns t
+ if it is a sub-alist.
+
+There is one simple element with negative POSITION; that's intended
+as a way for the user to ask to recalculate the buffer's index alist."
(or (and imenu--index-alist
(or (not imenu-auto-rescan)
(and imenu-auto-rescan
(> (buffer-size) imenu-auto-rescan-maxout))))
- ;; Get the index
- (setq imenu--index-alist
- (save-excursion
- (funcall imenu-create-index-function))))
+ ;; Get the index; truncate if necessary
+ (progn
+ (setq imenu--index-alist
+ (save-excursion
+ (save-restriction
+ (widen)
+ (funcall imenu-create-index-function))))
+ (imenu--truncate-items imenu--index-alist)))
(or imenu--index-alist noerror
(error "No items suitable for an index found in this buffer"))
(or imenu--index-alist
(setq imenu--index-alist (list nil)))
;; Add a rescan option to the index.
(cons imenu--rescan-item imenu--index-alist))
-;;;
+
;;; Find all markers in alist and makes
;;; them point nowhere.
-;;;
+;;; The top-level call uses nil as the argument;
+;;; non-nil arguments are in recursivecalls.
+(defvar imenu--cleanup-seen)
+
(defun imenu--cleanup (&optional alist)
- ;; Sets the markers in imenu--index-alist
- ;; point nowhere.
- ;; if alist is provided use that list.
- (or alist
- (setq alist imenu--index-alist))
+ ;; If alist is provided use that list.
+ ;; If not, empty the table of lists already seen
+ ;; and use imenu--index-alist.
+ (if alist
+ (setq imenu--cleanup-seen (cons alist imenu--cleanup-seen))
+ (setq alist imenu--index-alist imenu--cleanup-seen (list alist)))
+
(and alist
(mapcar
(function
(cond
((markerp (cdr item))
(set-marker (cdr item) nil))
- ((consp (cdr item))
+ ;; Don't process one alist twice.
+ ((memq (cdr item) imenu--cleanup-seen))
+ ((imenu--subalist-p item)
(imenu--cleanup (cdr item))))))
alist)
t))
(function
(lambda (item)
(cond
- ((listp (cdr item))
+ ((imenu--subalist-p item)
(append (list (setq counter (1+ counter))
(car item) 'keymap (car item))
(imenu--create-keymap-2 (cdr item) (+ counter 10) commands)))
(t
(let ((end (if commands `(lambda () (interactive)
(imenu--menubar-select ',item))
- (cons '(nil) t))))
+ (cons '(nil) item))))
(cons (car item)
(cons (car item) end))))
)))
tail (cdr elt)
alist (cdr alist)
head (car elt))
- (if (string= str head)
- (setq alist nil res elt)
- (if (and (listp tail)
- (setq res (imenu--in-alist str tail)))
- (setq alist nil))))
+ ;; A nested ALIST element looks like
+ ;; (INDEX-NAME (INDEX-NAME . INDEX-POSITION) ...)
+ ;; while a bottom-level element looks like
+ ;; (INDEX-NAME . INDEX-POSITION)
+ ;; We are only interested in the bottom-level elements, so we need to
+ ;; recurse if TAIL is a list.
+ (cond ((listp tail)
+ (if (setq res (imenu--in-alist str tail))
+ (setq alist nil)))
+ ((string= str head)
+ (setq alist nil res elt))))
res))
(defun imenu-default-create-index-function ()
(save-excursion
(setq name (funcall imenu-extract-index-name-function)))
(and (stringp name)
- (push (cons name (point)) index-alist)))
+ ;; [ydi] updated for imenu-use-markers
+ (push (cons name (if imenu-use-markers (point-marker) (point)))
+ index-alist)))
(imenu-progress-message prev-pos 100 t)
index-alist))
;; Use generic expression if possible.
((and imenu-generic-expression)
(imenu--generic-function imenu-generic-expression))
(t
- (error "The mode \"%s\" does not take full advantage of imenu.el yet."
- mode-name))))
+ (error "This buffer cannot use `imenu-default-create-index-function'"))))
(defun imenu--replace-spaces (name replacement)
;; Replace all spaces in NAME with REPLACEMENT.
(lambda (pat)
(let ((menu-title (car pat))
(regexp (cadr pat))
- (index (caddr pat)))
- (if (and (not found) ; Only allow one entry;
- (looking-at regexp))
- (let ((beg (match-beginning index))
- (end (match-end index)))
- (setq found t)
- (push
- (cons (buffer-substring-no-properties beg end) beg)
- (cdr
- (or (assoc menu-title index-alist)
- (car (push
- (cons menu-title '())
- index-alist))))))))))
+ (index (caddr pat))
+ (function (cadddr pat))
+ (rest (cddddr pat)))
+ (if (and (not found) ; Only allow one entry;
+ (looking-at regexp))
+ (let ((beg (match-beginning index))
+ (end (match-end index)))
+ (setq found t)
+ (push
+ (let ((name
+ (buffer-substring-no-properties beg end)))
+ ;; [ydi] updated for imenu-use-markers
+ (if imenu-use-markers
+ (setq beg (set-marker (make-marker) beg)))
+ (if function
+ (nconc (list name beg function)
+ rest)
+ (cons name beg)))
+ (cdr
+ (or (assoc menu-title index-alist)
+ (car (push
+ (cons menu-title '())
+ index-alist))))))))))
patterns))))
(imenu-progress-message prev-pos 100 t)
(let ((main-element (assq nil index-alist)))
- (nconc (delq main-element (delq 'dummy index-alist)) main-element))))
+ (nconc (delq main-element (delq 'dummy index-alist))
+ (cdr main-element)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
t)
(t
(setq choice (assoc name prepared-index-alist))
- (if (listp (cdr choice))
+ (if (imenu--subalist-p choice)
(imenu--completion-buffer (cdr choice) prompt)
choice)))))
INDEX-ALIST is the buffer index and EVENT is a mouse event.
-Returns t for rescan and otherwise a position number."
+Returns t for rescan and otherwise an element or subelement of INDEX-ALIST."
(setq index-alist (imenu--split-submenus index-alist))
- (let* ((menu (imenu--split-menu
- (if imenu-sort-function
- (sort
- (let ((res nil)
- (oldlist index-alist))
- ;; Copy list method from the cl package `copy-list'
- (while (consp oldlist) (push (pop oldlist) res))
- (prog1 (nreverse res) (setcdr res oldlist)))
- imenu-sort-function)
- index-alist)
+ (let* ((menu (imenu--split-menu index-alist
(or title (buffer-name))))
position)
- (and imenu-use-keymap-menu
- (setq menu (imenu--create-keymap-1 (car menu)
- (if (< 1 (length (cdr menu)))
- (cdr menu)
- (cdr (cadr menu))))))
+ (setq menu (imenu--create-keymap-1 (car menu)
+ (if (< 1 (length (cdr menu)))
+ (cdr menu)
+ (cdr (car (cdr menu))))))
(setq position (x-popup-menu event menu))
- (if imenu-use-keymap-menu
- (progn
- (cond
- ((and (listp position)
- (numberp (car position))
- (stringp (nth (1- (length position)) position)))
- (setq position (nth (1- (length position)) position)))
- ((and (stringp (car position))
- (null (cdr position)))
- (setq position (car position))))))
- (cond
- ((eq position nil)
- position)
- ((listp position)
- (imenu--mouse-menu position event
- (if title
- (concat title imenu-level-separator
- (car (rassq position index-alist)))
- (car (rassq position index-alist)))))
- ((stringp position)
- (or (string= position (car imenu--rescan-item))
- (imenu--in-alist position index-alist)))
- ((or (= position (cdr imenu--rescan-item))
- (and (stringp position)
- (string= position (car imenu--rescan-item))))
- t)
- (t
- (rassq position index-alist)))))
+ (cond ((eq position nil)
+ position)
+ ;; If one call to x-popup-menu handled the nested menus,
+ ;; find the result by looking down the menus here.
+ ((and (listp position)
+ (numberp (car position))
+ (stringp (nth (1- (length position)) position)))
+ (let ((final menu))
+ (while position
+ (setq final (assoc (car position) final))
+ (setq position (cdr position)))
+ (or (string= (car final) (car imenu--rescan-item))
+ (cdr (cdr (cdr final))))))
+ ;; If x-popup-menu went just one level and found a leaf item,
+ ;; return the INDEX-ALIST element for that.
+ ((and (consp position)
+ (stringp (car position))
+ (null (cdr position)))
+ (or (string= (car position) (car imenu--rescan-item))
+ (assq (car position) index-alist)))
+ ;; If x-popup-menu went just one level
+ ;; and found a non-leaf item (a submenu),
+ ;; recurse to handle the rest.
+ ((listp position)
+ (imenu--mouse-menu position event
+ (if title
+ (concat title imenu-level-separator
+ (car (rassq position index-alist)))
+ (car (rassq position index-alist))))))))
(defun imenu-choose-buffer-index (&optional prompt alist)
"Let the user select from a buffer index and return the chosen index.
completion buffer is always used, no matter if the mouse was used or
not.
-The returned value is on the form (INDEX-NAME . INDEX-POSITION)."
+The returned value is of the form (INDEX-NAME . INDEX-POSITION)."
(let (index-alist
(mouse-triggered (listp last-nonmenu-event))
(result t) )
NAME is a string used to name the menu bar item.
See the command `imenu' for more information."
(interactive "sImenu menu item name: ")
- (let ((newmap (make-sparse-keymap))
- (menu-bar (lookup-key (current-local-map) [menu-bar])))
- (define-key newmap [menu-bar]
- (append (make-sparse-keymap) menu-bar))
- (define-key newmap [menu-bar index]
- (cons name (nconc (make-sparse-keymap "Imenu")
- (make-sparse-keymap))))
- (use-local-map (append newmap (current-local-map))))
- (add-hook 'menu-bar-update-hook 'imenu-update-menubar))
+ (if (or (and (fboundp imenu-prev-index-position-function)
+ (fboundp imenu-extract-index-name-function))
+ (and imenu-generic-expression))
+ (let ((newmap (make-sparse-keymap))
+ (menu-bar (lookup-key (current-local-map) [menu-bar])))
+ (define-key newmap [menu-bar]
+ (append (make-sparse-keymap) menu-bar))
+ (define-key newmap [menu-bar index]
+ (cons name (nconc (make-sparse-keymap "Imenu")
+ (make-sparse-keymap))))
+ (use-local-map (append newmap (current-local-map)))
+ (add-hook 'menu-bar-update-hook 'imenu-update-menubar))
+ (error "The mode `%s' does not support Imenu" mode-name)))
(defvar imenu-buffer-menubar nil)
(let (menu menu1 old)
(setq imenu--last-menubar-index-alist index-alist)
(setq index-alist (imenu--split-submenus index-alist))
- (setq menu (imenu--split-menu
- (if imenu-sort-function
- (sort
- (let ((res nil)
- (oldlist index-alist))
- ;; Copy list method from the cl package `copy-list'
- (while (consp oldlist) (push (pop oldlist) res))
- (prog1 (nreverse res) (setcdr res oldlist)))
- imenu-sort-function)
- index-alist)
+ (setq menu (imenu--split-menu index-alist
(buffer-name)))
(setq menu1 (imenu--create-keymap-1 (car menu)
(if (< 1 (length (cdr menu)))
(cdr (car (cdr menu))))
t))
(setq old (lookup-key (current-local-map) [menu-bar index]))
- (if (keymapp old)
- (setcdr (nthcdr 2 old) menu1)))))))
+ (setcdr old (cdr menu1)))))))
(defun imenu--menubar-select (item)
"Use Imenu to select the function or variable named in this menu item."
- (imenu item))
+ (if (equal item imenu--rescan-item)
+ (progn
+ (imenu--cleanup)
+ (setq imenu--index-alist nil)
+ (imenu-update-menubar))
+ (imenu item)))
+
+(defun imenu-default-goto-function (name position &optional rest)
+ "This function is used for moving the point to POSITION.
+The NAME and REST parameters are not used, they are here just to make
+this function have the same interface as a function placed in a special
+index-item."
+ (if (or (< position (point-min))
+ (> position (point-max)))
+ ;; widen if outside narrowing
+ (widen))
+ (goto-char position))
;;;###autoload
(defun imenu (index-item)
"Jump to a place in the buffer chosen using a buffer menu or mouse menu.
See `imenu-choose-buffer-index' for more information."
- (interactive
- (list (save-restriction
- (widen)
- (car (imenu-choose-buffer-index)))))
+ (interactive (list (imenu-choose-buffer-index)))
;; Convert a string to an alist element.
(if (stringp index-item)
(setq index-item (assoc index-item (imenu--make-index-alist))))
(and index-item
(progn
(push-mark)
- (cond
- ((markerp (cdr index-item))
- (if (or ( > (marker-position (cdr index-item)) (point-min))
- ( < (marker-position (cdr index-item)) (point-max)))
- ;; widen if outside narrowing
- (widen))
- (goto-char (marker-position (cdr index-item))))
- (t
- (if (or ( > (cdr index-item) (point-min))
- ( < (cdr index-item) (point-max)))
- ;; widen if outside narrowing
- (widen))
- (goto-char (cdr index-item)))))))
+ (let* ((is-special-item (listp (cdr index-item)))
+ (function
+ (if is-special-item
+ (caddr index-item) imenu-default-goto-function))
+ (position (if is-special-item
+ (cadr index-item) (cdr index-item)))
+ (rest (if is-special-item (cddr index-item))))
+ (apply function (car index-item) position rest)))))
(provide 'imenu)