X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/7e563e040c12af531a905d4d780535c5c7f7b88b..3db571c0d71c8d568ff54786606cccad2b138ddc:/lisp/imenu.el diff --git a/lisp/imenu.el b/lisp/imenu.el index b04aecf3b5..1a107ed8ae 100644 --- a/lisp/imenu.el +++ b/lisp/imenu.el @@ -1,11 +1,13 @@ -;;; imenu.el --- Framework for mode-specific buffer indexes. +;;; imenu.el --- framework for mode-specific buffer indexes -;; Copyright (C) 1994, 1995, 1996 Free Software Foundation, Inc. +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 2002, 2003, 2004, +;; 2005 Free Software Foundation, Inc. ;; Author: Ake Stenhoff ;; Lars Lindberg +;; Maintainer: FSF ;; Created: 8 Feb 1994 -;; Keywords: tools +;; Keywords: tools convenience ;; This file is part of GNU Emacs. @@ -21,8 +23,8 @@ ;; 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. +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. ;;; Commentary: @@ -31,6 +33,8 @@ ;; A buffer index is an alist of names and buffer positions. ;; For instance all functions in a C-file and their positions. ;; +;; It is documented in the Emacs Lisp manual. +;; ;; How it works: ;; A mode-specific function is called to generate the index. It is @@ -44,10 +48,11 @@ ;; customize for other modes. A function for jumping to the chosen ;; index position is also supplied. -;;; Thanks goes to +;;; History: +;; Thanks go to ;; [simon] - Simon Leinen simon@lia.di.epfl.ch ;; [dean] - Dean Andrews ada@unison.com -;; [alon] - Alon Albert al@mercury.co.il +;; [alon] - Alon Albert al@mercury.co.il ;; [greg] - Greg Thompson gregt@porsche.visix.COM ;; [wolfgang] - Wolfgang Bangerth zcg51122@rpool1.rus.uni-stuttgart.de ;; [kai] - Kai Grossjohann grossjoh@linus.informatik.uni-dortmund.de @@ -55,7 +60,7 @@ ;; [christian] - Christian Egli Christian.Egli@hcsd.hac.com ;; [karl] - Karl Fogel kfogel@floss.life.uiuc.edu -;;; Code +;;; Code: (eval-when-compile (require 'cl)) @@ -65,22 +70,75 @@ ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar imenu-auto-rescan nil - "*Non-nil means Imenu should always rescan the buffers.") - -(defvar imenu-auto-rescan-maxout 60000 - "* auto-rescan is disabled in buffers larger than this. -This variable is buffer-local.") - -(defvar imenu-always-use-completion-buffer-p nil - "*Set this to non-nil for displaying the index in a completion buffer. +(defgroup imenu nil + "Mode-specific buffer indexes." + :group 'matching + :group 'frames + :group 'convenience + :link '(custom-manual "(elisp)Imenu")) + +(defcustom imenu-use-markers t + "*Non-nil means use markers instead of integers for Imenu buffer positions. + +Setting this to nil makes Imenu work a little faster but editing the +buffer will make the generated index positions wrong. + +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 '(choice integer + (const :tag "Unlimited")) + :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 (in bytes). +This variable is buffer-local." + :type 'integer + :group 'imenu) + +(defvar imenu-always-use-completion-buffer-p nil) +(make-obsolete-variable 'imenu-always-use-completion-buffer-p + 'imenu-use-popup-menu "22.1") + +(defcustom imenu-use-popup-menu + (if imenu-always-use-completion-buffer-p + (not (eq imenu-always-use-completion-buffer-p 'never)) + 'on-mouse) + "Use a popup menu rather than a minibuffer prompt. +If nil, always use a minibuffer prompt. +If t, always use a popup menu, +If `on-mouse' use a popup menu when `imenu' was invoked with the mouse." + :type '(choice (const :tag "On Mouse" on-mouse) + (const :tag "Never" nil) + (other :tag "Always" t)) + :group 'imenu) + +(defcustom imenu-eager-completion-buffer + (not (eq imenu-always-use-completion-buffer-p 'never)) + "If non-nil, eagerly popup the completion buffer." + :type 'boolean + :group 'imenu + :version "22.1") + +(defcustom imenu-after-jump-hook nil + "*Hooks called after jumping to a place in the buffer. + +Useful things to use here include `reposition-window', `recenter', and +\(lambda () (recenter 0)) to show at top of screen." + :type 'hook + :group 'imenu) -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.") - -(defvar imenu-sort-function nil +;;;###autoload +(defcustom imenu-sort-function nil "*The function to use for sorting the index mouse-menu. Affects only the mouse index menu. @@ -91,70 +149,64 @@ in the buffer. Set it to `imenu--sort-by-name' if you want alphabetic sorting. -The function should take two arguments and return T if the first +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.") - -(defvar imenu-max-items 25 - "*Maximum number of elements in an mouse menu for Imenu.") - -(defvar 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 - -Relevant only if the mode-specific function that creates the buffer -index use `imenu-progress-message'.") - -(defvar imenu-space-replacement "^" +\(NAME . POSITION). Look at `imenu--sort-by-name' for an example." + :type '(choice (const :tag "No sorting" nil) + (const :tag "Sort by name" imenu--sort-by-name) + (function :tag "Another function")) + :group 'imenu) + +(defcustom imenu-max-items 25 + "*Maximum number of elements in a mouse menu for Imenu." + :type 'integer + :group 'imenu) + +;; No longer used. KFS 2004-10-27 +;; (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. +;; +;; Relevant only if the mode-specific function that creates the buffer +;; index use `imenu-progress-message', and not useful if that is fast, in +;; which case you might as well set this to nil." +;; :type '(choice string +;; (const :tag "None" nil)) +;; :group 'imenu) + +(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.") +Used when presenting the index in a completion buffer to make the +names work as tokens." + :type '(choice string (const nil)) + :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' -to create a buffer 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. - -REGEXP is a regexp that should match a construct in the buffer that is -to be displayed in the menu; i.e., function or variable definitions, -etc. It contains a substring which is the name to appear in the -menu. See the info section on Regexps for more information. - -INDEX points to the substring in REGEXP that contains the name (of the -function, variable or type) that is to appear in the menu. - -For emacs-lisp-mode for example PATTERN would look like: - -'((nil \"^\\\\s-*(def\\\\(un\\\\|subst\\\\|macro\\\\|advice\\\\)\\\\s-+\\\\([-A-Za-z0-9+]+\\\\)\" 2) - (\"*Vars*\" \"^\\\\s-*(def\\\\(var\\\\|const\\\\)\\\\s-+\\\\([-A-Za-z0-9+]+\\\\)\" 2) - (\"*Types*\" \"^\\\\s-*(def\\\\(type\\\\|struct\\\\|class\\\\|ine-condition\\\\)\\\\s-+\\\\([-A-Za-z0-9+]+\\\\)\" 2)) +If non-nil this pattern is passed to `imenu--generic-function' to +create a buffer index. Look there for the documentation of this +pattern's structure. -The variable is buffer-local.") +For example, see the value of `fortran-imenu-generic-expression' used by +`fortran-mode' with `imenu-syntax-alist' set locally to give the +characters which normally have \"symbol\" syntax \"word\" syntax +during matching.") ;;;###autoload (make-variable-buffer-local 'imenu-generic-expression) ;;;; Hooks +;;;###autoload (defvar imenu-create-index-function 'imenu-default-create-index-function "The function to use for creating a buffer index. @@ -162,16 +214,16 @@ It should be a function that takes no arguments and returns an index 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...). +Special elements look like (INDEX-NAME INDEX-POSITION 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'. +if it is a sub-alist. -The variable is buffer-local.") +This function is called within a `save-excursion'.") +;;;###autoload (make-variable-buffer-local 'imenu-create-index-function) +;;;###autoload (defvar imenu-prev-index-position-function 'beginning-of-defun "Function for finding the next index position. @@ -182,36 +234,67 @@ file. The function should leave point at the place to be connected to the index and it should return nil when it doesn't find another index.") +;;;###autoload (make-variable-buffer-local 'imenu-prev-index-position-function) +;;;###autoload (defvar imenu-extract-index-name-function nil - "Function for extracting the index name. + "Function for extracting the index item name, given a position. -This function is called after the function pointed out by -`imenu-prev-index-position-function'.") +This function is called after `imenu-prev-index-position-function' +finds a position for an index item, with point at that position. +It should return the name for that index item.") +;;;###autoload (make-variable-buffer-local 'imenu-extract-index-name-function) +;;;###autoload +(defvar imenu-name-lookup-function nil + "Function to compare string with index item. + +This function will be called with two strings, and should return +non-nil if they match. + +If nil, comparison is done with `string='. +Set this to some other function for more advanced comparisons, +such as \"begins with\" or \"name matches and number of +arguments match\".") +;;;###autoload +(make-variable-buffer-local 'imenu-name-lookup-function) + +;;;###autoload +(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.") +;;;###autoload +(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)))) + (not (eq (car (cadr item)) 'lambda)))) -;;; -;;; Macro to display a progress message. -;;; RELPOS is the relative position to display. -;;; If RELPOS is nil, then the relative position in the buffer -;;; is calculated. -;;; PREVPOS is the variable in which we store the last position displayed. +;; Macro to display a progress message. +;; RELPOS is the relative position to display. +;; If RELPOS is nil, then the relative position in the buffer +;; is calculated. +;; PREVPOS is the variable in which we store the last position displayed. (defmacro imenu-progress-message (prevpos &optional relpos reverse) - (` (and - imenu-scanning-message - (let ((pos (, (if relpos - relpos - (` (imenu--relative-position (, reverse))))))) - (if (, (if relpos t - (` (> pos (+ 5 (, prevpos)))))) - (progn - (message imenu-scanning-message pos) - (setq (, prevpos) pos))))))) + +;; Made obsolete/empty, as computers are now faster than the eye, and +;; it had problems updating the messages correctly, and could shadow +;; more important messages/prompts in the minibuffer. KFS 2004-10-27. + +;; `(and +;; imenu-scanning-message +;; (let ((pos ,(if relpos +;; relpos +;; `(imenu--relative-position ,reverse)))) +;; (if ,(if relpos t +;; `(> pos (+ 5 ,prevpos))) +;; (progn +;; (message imenu-scanning-message pos) +;; (setq ,prevpos pos))))) +) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -221,21 +304,23 @@ This function is called after the function pointed out by ;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Return the current/previous sexp and the location of the sexp (its -;; beginning) without moving the point. +;; FIXME: This is the only imenu-example-* definition that's actually used, +;; and it seems to only be used by cperl-mode.el. We should just move it to +;; cperl-mode.el and remove the rest. (defun imenu-example--name-and-position () + "Return the current/previous sexp and its (beginning) location. +Don't move point." (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 -;;; +;;; (defun imenu-example--lisp-extract-index-name () ;; Example of a candidate for `imenu-extract-index-name-function'. @@ -264,31 +349,31 @@ This function is called after the function pointed out by ;; Search for the function (while (beginning-of-defun) (imenu-progress-message prev-pos nil t) - (save-match-data - (and (looking-at "(def") - (save-excursion + (save-match-data + (and (looking-at "(def") + (save-excursion (down-list 1) - (cond + (cond ((looking-at "def\\(var\\|const\\)") - (forward-sexp 2) - (push (imenu-example--name-and-position) - index-var-alist)) + (forward-sexp 2) + (push (imenu-example--name-and-position) + index-var-alist)) ((looking-at "def\\(un\\|subst\\|macro\\|advice\\)") - (forward-sexp 2) - (push (imenu-example--name-and-position) - index-alist)) + (forward-sexp 2) + (push (imenu-example--name-and-position) + index-alist)) ((looking-at "def\\(type\\|struct\\|class\\|ine-condition\\)") - (forward-sexp 2) + (forward-sexp 2) (if (= (char-after (1- (point))) ?\)) - (progn + (progn (forward-sexp -1) - (down-list 1) + (down-list 1) (forward-sexp 1))) - (push (imenu-example--name-and-position) - index-type-alist)) - (t - (forward-sexp 2) - (push (imenu-example--name-and-position) + (push (imenu-example--name-and-position) + index-type-alist)) + (t + (forward-sexp 2) + (push (imenu-example--name-and-position) index-unknown-alist))))))) (imenu-progress-message prev-pos 100) (and index-var-alist @@ -304,7 +389,7 @@ This function is called after the function pointed out by ;; Regular expression to find C functions (defvar imenu-example--function-name-regexp-c - (concat + (concat "^[a-zA-Z0-9]+[ \t]?" ; type specs; there can be no "\\([a-zA-Z0-9_*]+[ \t]+\\)?" ; more than 3 tokens, right? "\\([a-zA-Z0-9_*]+[ \t]+\\)?" @@ -345,11 +430,17 @@ This function is called after the function pointed out by ;; The latest buffer index. ;; Buffer local. -(defvar imenu--index-alist nil) +(defvar imenu--index-alist nil + "The buffer index computed for this buffer in Imenu. +Simple elements in the alist look like (INDEX-NAME . INDEX-POSITION). +Special elements look like (INDEX-NAME INDEX-POSITION FUNCTION ARGUMENTS...). +A nested sub-alist element looks like (INDEX-NAME SUB-ALIST).") + (make-variable-buffer-local 'imenu--index-alist) -;; The latest buffer index used to update the menu bar menu. -(defvar imenu--last-menubar-index-alist nil) +(defvar imenu--last-menubar-index-alist nil + "The latest buffer index used to update the menu bar menu.") + (make-variable-buffer-local 'imenu--last-menubar-index-alist) ;; History list for 'jump-to-function-in-buffer'. @@ -365,11 +456,14 @@ This function is called after the function pointed out by ;;; ;;; Sort function ;;; Sorts the items depending on their index name. -;;; An item look like (NAME . POSITION). +;;; An item looks like (NAME . POSITION). ;;; (defun imenu--sort-by-name (item1 item2) (string-lessp (car item1) (car item2))) +(defun imenu--sort-by-position (item1 item2) + (< (cdr item1) (cdr item2))) + (defun imenu--relative-position (&optional reverse) ;; Support function to calculate relative position in buffer ;; Beginning of buffer is 0 and end of buffer is 100 @@ -411,50 +505,58 @@ This function is called after the function pointed out by (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))) + (dolist (item tail) + (when (imenu--subalist-p item) + (push item keep-at-top) + (setq menulist (delq item menulist)))) (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))) + (setq menulist (sort (copy-sequence menulist) 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))))) + (setq menulist + (mapcar + (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. (defun imenu--split-submenus (alist) - (mapcar (function (lambda (elt) - (if (and (consp elt) - (stringp (car elt)) - (listp (cdr elt))) - (imenu--split-menu (cdr elt) (car elt)) - elt))) + (mapcar (function + (lambda (elt) + (if (and (consp elt) + (stringp (car elt)) + (listp (cdr elt))) + (imenu--split-menu (cdr elt) (car elt)) + elt))) alist)) +;;; 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))) + ;; truncate if necessary + ((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 an index-alist for the definitions in the current buffer. +Report an error if the list is empty unless NOERROR is supplied and +non-nil. + 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. +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." @@ -462,10 +564,14 @@ as a way for the user to ask to recalculate the buffer's 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 @@ -480,7 +586,7 @@ as a way for the user to ask to recalculate the buffer's index alist." (defvar imenu--cleanup-seen) (defun imenu--cleanup (&optional alist) - ;; If alist is provided use that list. + ;; If alist is provided use that list. ;; If not, empty the table of lists already seen ;; and use imenu--index-alist. (if alist @@ -488,54 +594,40 @@ as a way for the user to ask to recalculate the buffer's index alist." (setq alist imenu--index-alist imenu--cleanup-seen (list alist))) (and alist - (mapcar - (function - (lambda (item) - (cond - ((markerp (cdr item)) - (set-marker (cdr item) nil)) - ;; Don't process one alist twice. - ((memq (cdr item) imenu--cleanup-seen)) - ((imenu--subalist-p item) - (imenu--cleanup (cdr item)))))) + (mapc + (lambda (item) + (cond + ((markerp (cdr item)) + (set-marker (cdr item) nil)) + ;; Don't process one alist twice. + ((memq (cdr item) imenu--cleanup-seen)) + ((imenu--subalist-p item) + (imenu--cleanup (cdr item))))) alist) t)) -(defun imenu--create-keymap-2 (alist counter &optional commands) - (let ((map nil)) - (mapcar - (function - (lambda (item) - (cond - ((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) item)))) - (cons (car item) - (cons (car item) end)))) - ))) - alist))) - -;; If COMMANDS is non-nil, make a real keymap -;; with a real command used as the definition. -;; If it is nil, make something suitable for x-popup-menu. -(defun imenu--create-keymap-1 (title alist &optional commands) - (append (list 'keymap title) (imenu--create-keymap-2 alist 0 commands))) - +(defun imenu--create-keymap (title alist &optional cmd) + (list* 'keymap title + (mapcar + (lambda (item) + (list* (car item) (car item) + (cond + ((imenu--subalist-p item) + (imenu--create-keymap (car item) (cdr item) cmd)) + (t + `(lambda () (interactive) + ,(if cmd `(,cmd ',item) (list 'quote item))))))) + alist))) (defun imenu--in-alist (str alist) "Check whether the string STR is contained in multi-level ALIST." (let (elt head tail res) (setq res nil) (while alist - (setq elt (car alist) + (setq elt (car alist) tail (cdr elt) - alist (cdr alist) - head (car elt)) + alist (cdr alist) + head (car elt)) ;; A nested ALIST element looks like ;; (INDEX-NAME (INDEX-NAME . INDEX-POSITION) ...) ;; while a bottom-level element looks like @@ -545,10 +637,25 @@ as a way for the user to ask to recalculate the buffer's index alist." (cond ((listp tail) (if (setq res (imenu--in-alist str tail)) (setq alist nil))) - ((string= str head) + ((if imenu-name-lookup-function + (funcall imenu-name-lookup-function str head) + (string= str head)) (setq alist nil res elt)))) res)) +(defvar imenu-syntax-alist nil + "Alist of syntax table modifiers to use while in `imenu--generic-function'. + +The car of the assocs may be either a character or a string and the +cdr is a syntax description appropriate for `modify-syntax-entry'. For +a string, all the characters in the string get the specified syntax. + +This is typically used to give word syntax to characters which +normally have symbol syntax to simplify `imenu-expression' +and speed-up matching.") +;;;###autoload +(make-variable-buffer-local 'imenu-syntax-alist) + (defun imenu-default-create-index-function () "*Wrapper for index searching functions. @@ -557,143 +664,185 @@ Moves point to end of buffer and then repeatedly calls Their results are gathered into an index alist." ;; These should really be done by setting imenu-create-index-function ;; in these major modes. But save that change for later. - (cond ((and (fboundp imenu-prev-index-position-function) - (fboundp imenu-extract-index-name-function)) + (cond ((and imenu-prev-index-position-function + imenu-extract-index-name-function) (let ((index-alist '()) prev-pos name) (goto-char (point-max)) (imenu-progress-message prev-pos 0 t) ;; Search for the function - (while (funcall imenu-prev-index-position-function) + (while (funcall imenu-prev-index-position-function) (imenu-progress-message prev-pos nil t) (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)) + (imenu--generic-function imenu-generic-expression)) (t - (error "The mode \"%s\" does not take full advantage of imenu.el yet." - mode-name)))) - -(defun imenu--replace-spaces (name replacement) - ;; Replace all spaces in NAME with REPLACEMENT. - ;; That second argument should be a string. - (mapconcat - (function - (lambda (ch) - (if (char-equal ch ?\ ) - replacement - (char-to-string ch)))) - name - "")) - -(defun imenu--flatten-index-alist (index-alist &optional concat-names prefix) - ;; Takes a nested INDEX-ALIST and returns a flat index alist. - ;; If optional CONCAT-NAMES is non-nil, then a nested index has its - ;; name and a space concatenated to the names of the children. - ;; Third argument PREFIX is for internal use only. - (mapcan - (function - (lambda (item) - (let* ((name (car item)) - (pos (cdr item)) - (new-prefix (and concat-names - (if prefix - (concat prefix imenu-level-separator name) - name)))) - (cond - ((or (markerp pos) (numberp pos)) - (list (cons new-prefix pos))) - (t - (imenu--flatten-index-alist pos new-prefix)))))) - index-alist)) + (error "This buffer cannot use `imenu-default-create-index-function'")))) + +;; Not used and would require cl at run time +;; (defun imenu--flatten-index-alist (index-alist &optional concat-names prefix) +;; ;; Takes a nested INDEX-ALIST and returns a flat index alist. +;; ;; If optional CONCAT-NAMES is non-nil, then a nested index has its +;; ;; name and a space concatenated to the names of the children. +;; ;; Third argument PREFIX is for internal use only. +;; (mapcan +;; (lambda (item) +;; (let* ((name (car item)) +;; (pos (cdr item)) +;; (new-prefix (and concat-names +;; (if prefix +;; (concat prefix imenu-level-separator name) +;; name)))) +;; (cond +;; ((or (markerp pos) (numberp pos)) +;; (list (cons new-prefix pos))) +;; (t +;; (imenu--flatten-index-alist pos new-prefix))))) +;; index-alist)) ;;; ;;; Generic index gathering function. ;;; +(defvar imenu-case-fold-search t + "Defines whether `imenu--generic-function' should fold case when matching. + +This variable should be set (only) by initialization code +for modes which use `imenu--generic-function'. If it is not set, but +`font-lock-defaults' is set, then font-lock's setting is used.") +;;;###autoload +(make-variable-buffer-local 'imenu-case-fold-search) + +;; This function can be called with quitting disabled, +;; so it needs to be careful never to loop! (defun imenu--generic-function (patterns) -;; Built on some ideas that Erik Naggum once posted -;; to comp.emacs "Return an index of the current buffer as an alist. -PATTERN is an alist with elements that look like this: (MENU-TITLE -REGEXP INDEX). +PATTERNS is 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 POSITION-MARKER FUNCTION +ARGUMENTS...) with FUNCTION and ARGUMENTS 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. +MENU-TITLE is a string used as the title for the submenu or nil +if the entries are not nested. -REGEXP is a regexp that should match a construct in the buffer that is -to be displayed in the menu; i.e., function or variable definitions, -etc. It contains a substring which is the name to appear in the -menu. See the info section on Regexps for more information. +REGEXP is a regexp that should match a construct in the buffer +that is to be displayed in the menu; i.e., function or variable +definitions, etc. It contains a substring which is the name to +appear in the menu. See the info section on Regexps for more +information. REGEXP may also be a function, called without +arguments. It is expected to search backwards. It shall return +true and set `match-data' iff it finds another element. -INDEX points to the substring in REGEXP that contains the name (of the -function, variable or type) that is to appear in the menu. +INDEX points to the substring in REGEXP that contains the +name (of the function, variable or type) that is to appear in the +menu. -For emacs-lisp-mode for example PATTERN would look like: +The variable `imenu-case-fold-search' determines whether or not the +regexp matches are case sensitive, and `imenu-syntax-alist' can be +used to alter the syntax table for the search. -'((nil \"^\\\\s-*(def\\\\(un\\\\|subst\\\\|macro\\\\|advice\\\\)\\\\s-+\\\\([-A-Za-z0-9]+\\\\)\" 2) - (\"*Vars*\" \"^\\\\s-*(def\\\\(var\\\\|const\\\\)\\\\s-+\\\\([-A-Za-z0-9]+\\\\)\" 2) - (\"*Types*\" \"^\\\\s-*(def\\\\(type\\\\|struct\\\\|class\\\\|ine-condition\\\\)\\\\s-+\\\\([-A-Za-z0-9]+\\\\)\" 2))' +See `lisp-imenu-generic-expression' for an example of PATTERNS. Returns an index of the current buffer as an alist. The elements in -the alist look like: (INDEX-NAME . INDEX-POSITION). They may also be -nested index lists like (INDEX-NAME . INDEX-ALIST) depending on -pattern. - -\(imenu--generic-function PATTERN\)." +the alist look like: + (INDEX-NAME . INDEX-POSITION) +or like: + (INDEX-NAME INDEX-POSITION FUNCTION ARGUMENTS...) +They may also be nested index alists like: + (INDEX-NAME . INDEX-ALIST) +depending on PATTERNS." (let ((index-alist (list 'dummy)) - (found nil) - (global-regexp - (concat "\\(" - (mapconcat - (function (lambda (pattern) (identity (cadr pattern)))) - patterns "\\)\\|\\(") - "\\)")) - prev-pos) - + prev-pos + (case-fold-search (if (or (local-variable-p 'imenu-case-fold-search) + (not (local-variable-p 'font-lock-defaults))) + imenu-case-fold-search + (nth 2 font-lock-defaults))) + (old-table (syntax-table)) + (table (copy-syntax-table (syntax-table))) + (slist imenu-syntax-alist)) + ;; Modify the syntax table used while matching regexps. + (dolist (syn slist) + ;; The character(s) to modify may be a single char or a string. + (if (numberp (car syn)) + (modify-syntax-entry (car syn) (cdr syn) table) + (mapc (lambda (c) + (modify-syntax-entry c (cdr syn) table)) + (car syn)))) (goto-char (point-max)) (imenu-progress-message prev-pos 0 t) - (save-match-data - (while (re-search-backward global-regexp nil t) - (imenu-progress-message prev-pos nil t) - (setq found nil) - (save-excursion - (goto-char (match-beginning 0)) - (mapcar - (function - (lambda (pat) - (let ((menu-title (car pat)) - (regexp (cadr pat)) - (index (caddr pat)) - (function (cadddr pat)) - (rest (cddddr pat))) - (if (and (not found) ; Only allow one entry; - (looking-at regexp)) - (let ((beg (make-marker)) - (end (match-end index))) - (set-marker beg (match-beginning index)) - (setq found t) - (push - (let ((name - (buffer-substring-no-properties beg end))) - (if function - (nconc (list name function name beg) - rest) - (cons name beg))) - (cdr - (or (assoc menu-title index-alist) - (car (push - (cons menu-title '()) - index-alist)))))))))) - patterns)))) + (unwind-protect ; for syntax table + (save-match-data + (set-syntax-table table) + + ;; map over the elements of imenu-generic-expression + ;; (typically functions, variables ...) + (dolist (pat patterns) + (let ((menu-title (car pat)) + (regexp (nth 1 pat)) + (index (nth 2 pat)) + (function (nth 3 pat)) + (rest (nthcdr 4 pat)) + start beg) + ;; Go backwards for convenience of adding items in order. + (goto-char (point-max)) + (while (and (if (functionp regexp) + (funcall regexp) + (re-search-backward regexp nil t)) + ;; Exit the loop if we get an empty match, + ;; because it means a bad regexp was specified. + (not (= (match-beginning 0) (match-end 0)))) + (setq start (point)) + ;; Record the start of the line in which the match starts. + ;; That's the official position of this definition. + (goto-char (match-beginning index)) + (beginning-of-line) + (setq beg (point)) + (imenu-progress-message prev-pos nil t) + ;; Add this sort of submenu only when we've found an + ;; item for it, avoiding empty, duff menus. + (unless (assoc menu-title index-alist) + (push (list menu-title) index-alist)) + (if imenu-use-markers + (setq beg (copy-marker beg))) + (let ((item + (if function + (nconc (list (match-string-no-properties index) + beg function) + rest) + (cons (match-string-no-properties index) + beg))) + ;; This is the desired submenu, + ;; starting with its title (or nil). + (menu (assoc menu-title index-alist))) + ;; Insert the item unless it is already present. + (unless (member item (cdr menu)) + (setcdr menu + (cons item (cdr menu))))) + ;; Go to the start of the match, to make sure we + ;; keep making progress backwards. + (goto-char start)))) + (set-syntax-table old-table))) (imenu-progress-message prev-pos 100 t) + ;; Sort each submenu by position. + ;; This is in case one submenu gets items from two different regexps. + (dolist (item index-alist) + (when (listp item) + (setcdr item (sort (cdr item) 'imenu--sort-by-position)))) (let ((main-element (assq nil index-alist))) (nconc (delq main-element (delq 'dummy index-alist)) (cdr main-element))))) @@ -704,47 +853,53 @@ pattern. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; See also info-lookup-find-item +(defun imenu-find-default (guess completions) + "Fuzzily find an item based on GUESS inside the alist COMPLETIONS." + (catch 'found + (let ((case-fold-search t)) + (if (assoc guess completions) guess + (dolist (re (list (concat "\\`" (regexp-quote guess) "\\'") + (concat "\\`" (regexp-quote guess)) + (concat (regexp-quote guess) "\\'") + (regexp-quote guess))) + (dolist (x completions) + (if (string-match re (car x)) (throw 'found (car x))))))))) + (defun imenu--completion-buffer (index-alist &optional prompt) "Let the user select from INDEX-ALIST in a completion buffer with PROMPT. -Returns t for rescan and otherwise a position number." +Return one of the entries in index-alist or nil." ;; Create a list for this buffer only when needed. - (let (name choice - (prepared-index-alist - (mapcar - (function - (lambda (item) - (cons (imenu--replace-spaces (car item) imenu-space-replacement) - (cdr item)))) - index-alist))) - (if (eq imenu-always-use-completion-buffer-p 'never) - (setq name (completing-read (or prompt "Index item: ") - prepared-index-alist - nil t nil 'imenu--history-list)) - (save-window-excursion - ;; Display the completion buffer - (with-output-to-temp-buffer "*Completions*" - (display-completion-list - (all-completions "" prepared-index-alist ))) - (let ((minibuffer-setup-hook - (function (lambda () - (let ((buffer (current-buffer))) - (save-excursion - (set-buffer "*Completions*") - (setq completion-reference-buffer buffer))))))) - ;; Make a completion question - (setq name (completing-read (or prompt "Index item: ") - prepared-index-alist - nil t nil 'imenu--history-list))))) - (cond ((not (stringp name)) - nil) - ((string= name (car imenu--rescan-item)) - t) - (t - (setq choice (assoc name prepared-index-alist)) - (if (imenu--subalist-p choice) - (imenu--completion-buffer (cdr choice) prompt) - choice))))) + (let ((name (thing-at-point 'symbol)) + choice + (prepared-index-alist + (if (not imenu-space-replacement) index-alist + (mapcar + (lambda (item) + (cons (subst-char-in-string ?\s (aref imenu-space-replacement 0) + (car item)) + (cdr item))) + index-alist)))) + (when (stringp name) + (setq name (or (imenu-find-default name prepared-index-alist) name))) + (cond (prompt) + ((and name (imenu--in-alist name prepared-index-alist)) + (setq prompt (format "Index item (default %s): " name))) + (t (setq prompt "Index item: "))) + (let ((minibuffer-setup-hook minibuffer-setup-hook)) + ;; Display the completion buffer. + (if (not imenu-eager-completion-buffer) + (add-hook 'minibuffer-setup-hook 'minibuffer-completion-help)) + (setq name (completing-read prompt + prepared-index-alist + nil t nil 'imenu--history-list name))) + + (when (stringp name) + (setq choice (assoc name prepared-index-alist)) + (if (imenu--subalist-p choice) + (imenu--completion-buffer (cdr choice) prompt) + choice)))) (defun imenu--mouse-menu (index-alist event &optional title) "Let the user select from a buffer index from a mouse menu. @@ -753,43 +908,12 @@ INDEX-ALIST is the buffer index and EVENT is a mouse event. 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 index-alist - (or title (buffer-name)))) - position) - (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)) - (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)))))))) + (let* ((menu (imenu--split-menu index-alist (or title (buffer-name)))) + (map (imenu--create-keymap (car menu) + (cdr (if (< 1 (length (cdr menu))) + menu + (car (cdr menu))))))) + (popup-menu map event))) (defun imenu-choose-buffer-index (&optional prompt alist) "Let the user select from a buffer index and return the chosen index. @@ -804,14 +928,14 @@ select from ALIST. With no index alist ALIST, it calls `imenu--make-index-alist' to create the index alist. -If `imenu-always-use-completion-buffer-p' is non-nil, then the +If `imenu-use-popup-menu' is non-nil, then the completion buffer is always used, no matter if the mouse was used or not. The returned value is of the form (INDEX-NAME . INDEX-POSITION)." (let (index-alist (mouse-triggered (listp last-nonmenu-event)) - (result t) ) + (result t)) ;; If selected by mouse, see to that the window where the mouse is ;; really is selected. (and mouse-triggered @@ -822,95 +946,126 @@ The returned value is of the form (INDEX-NAME . INDEX-POSITION)." (while (eq result t) (setq index-alist (if alist alist (imenu--make-index-alist))) (setq result - (if (and mouse-triggered - (not imenu-always-use-completion-buffer-p)) + (if (and imenu-use-popup-menu + (or (eq imenu-use-popup-menu t) mouse-triggered)) (imenu--mouse-menu index-alist last-nonmenu-event) (imenu--completion-buffer index-alist prompt))) - (and (eq result t) + (and (equal result imenu--rescan-item) (imenu--cleanup) - (setq imenu--index-alist nil))) + (setq result t imenu--index-alist nil))) result)) ;;;###autoload (defun imenu-add-to-menubar (name) - "Adds an `imenu' entry to the menu bar for the current buffer. + "Add an `imenu' entry to the menu bar for the current buffer. 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 imenu-prev-index-position-function + imenu-extract-index-name-function) + imenu-generic-expression + (not (eq imenu-create-index-function + 'imenu-default-create-index-function))) + (let ((newmap (make-sparse-keymap))) + (set-keymap-parent newmap (current-local-map)) + (setq imenu--last-menubar-index-alist nil) + (define-key newmap [menu-bar index] + `(menu-item ,name ,(make-sparse-keymap "Imenu"))) + (use-local-map newmap) + (add-hook 'menu-bar-update-hook 'imenu-update-menubar)) + (error "The mode `%s' does not support Imenu" mode-name))) + +;;;###autoload +(defun imenu-add-menubar-index () + "Add an Imenu \"Index\" entry on the menu bar for the current buffer. + +A trivial interface to `imenu-add-to-menubar' suitable for use in a hook." + (interactive) + (imenu-add-to-menubar "Index")) (defvar imenu-buffer-menubar nil) +(defvar imenu-menubar-modified-tick 0 + "The value of (buffer-modified-tick) as of last call to `imenu-update-menubar'.") +(make-variable-buffer-local 'imenu-menubar-modified-tick) + (defun imenu-update-menubar () - (and (current-local-map) - (keymapp (lookup-key (current-local-map) [menu-bar index])) - (let ((index-alist (imenu--make-index-alist t))) - ;; Don't bother updating if the index-alist has not changed - ;; since the last time we did it. - (or (equal index-alist imenu--last-menubar-index-alist) - (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 index-alist - (buffer-name))) - (setq menu1 (imenu--create-keymap-1 (car menu) - (if (< 1 (length (cdr menu))) - (cdr menu) - (cdr (car (cdr menu)))) - t)) - (setq old (lookup-key (current-local-map) [menu-bar index])) - (setcdr old (cdr menu1))))))) + (when (and (current-local-map) + (keymapp (lookup-key (current-local-map) [menu-bar index])) + (not (eq (buffer-modified-tick) + imenu-menubar-modified-tick))) + (setq imenu-menubar-modified-tick (buffer-modified-tick)) + (let ((index-alist (imenu--make-index-alist t))) + ;; Don't bother updating if the index-alist has not changed + ;; since the last time we did it. + (unless (equal index-alist imenu--last-menubar-index-alist) + (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 index-alist + (buffer-name))) + (setq menu1 (imenu--create-keymap (car menu) + (cdr (if (< 1 (length (cdr menu))) + menu + (car (cdr menu)))) + 'imenu--menubar-select)) + (setq old (lookup-key (current-local-map) [menu-bar index])) + (setcdr old (cdr menu1))))))) (defun imenu--menubar-select (item) - "Use Imenu to select the function or variable named in this menu item." - (if (equal item '("*Rescan*" . -99)) + "Use Imenu to select the function or variable named in this menu ITEM." + (if (equal item imenu--rescan-item) (progn (imenu--cleanup) + ;; Make sure imenu-update-menubar redoes everything. + (setq imenu-menubar-modified-tick -1) (setq imenu--index-alist nil) - (imenu-update-menubar)) - (imenu item))) + (setq imenu--last-menubar-index-alist nil) + (imenu-update-menubar) + t) + (imenu item) + nil)) + +(defun imenu-default-goto-function (name position &optional rest) + "Move to the given position. + +NAME is ignored. POSITION is where to move. REST is also ignored. +The ignored args just 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) - (imenu-choose-buffer-index)))) +INDEX-ITEM specifies the position. See `imenu-choose-buffer-index' +for more information." + (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)))) - ((imenu--subalist-p index-item) - (if (or (< (cdr index-item) (point-min)) - (> (cdr index-item) (point-max))) - ;; widen if outside narrowing - (widen)) - (goto-char (cdr index-item))) - (t - ;; A special item with a function. - (let ((function (cadr index-item)) - (rest (cddr index-item))) - (apply function (car index-item) rest))))))) + (when index-item + (push-mark) + (let* ((is-special-item (listp (cdr index-item))) + (function + (if is-special-item + (nth 2 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)) + (run-hooks 'imenu-after-jump-hook))) + +(dolist (mess + '("^No items suitable for an index found in this buffer$" + "^This buffer cannot use `imenu-default-create-index-function'$" + "^The mode `.*' does not support Imenu$")) + (add-to-list 'debug-ignored-errors mess)) (provide 'imenu) +;; arch-tag: 98a2f5f5-4b91-4704-b18c-3aacf77d77a7 ;;; imenu.el ends here