X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/4a840d8bf75c820f240996248f4921258de91177..dc6a28319312fe81f7a1015e363174022313f0bd:/lisp/imenu.el diff --git a/lisp/imenu.el b/lisp/imenu.el dissimilarity index 60% index 715dcde377..f4c378a5e1 100644 --- a/lisp/imenu.el +++ b/lisp/imenu.el @@ -1,1021 +1,1058 @@ -;;; imenu.el --- Framework for mode-specific buffer indexes. - -;; Copyright (C) 1994 Free Software Foundation, Inc. - -;; Author: Ake Stenhoff -;; Lars Lindberg -;; Created: 8 Feb 1994 -;; Version: 1.17 -;; Keywords: tools -;; -;; This program 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 program 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 this program; if not, write to the Free Software -;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -;;; Commentary: -;; -;; Purpose of this package: -;; To present a framework for mode-specific buffer indexes. -;; A buffer index is an alist of names and buffer positions. -;; For instance all functions in a C-file and their positions. -;; -;; How it works: - -;; A mode-specific function is called to generate the index. It is -;; then presented to the user, who can choose from this index. -;; -;; The package comes with a set of example functions for how to -;; utilize this package. - -;; There are *examples* for index gathering functions/regular -;; expressions for C/C++ and Lisp/Emacs Lisp but it is easy to -;; customize for other modes. A function for jumping to the chosen -;; index position is also supplied. - -;;; Thanks goes to -;; [simon] - Simon Leinen simon@lia.di.epfl.ch -;; [dean] - Dean Andrews ada@unison.com -;; [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 -;; [david] - David M. Smith dsmith@stats.adelaide.edu.au -;; [christian] - Christian Egli Christian.Egli@hcsd.hac.com -;; [karl] - Karl Fogel kfogel@floss.life.uiuc.edu - -;;; Code -(eval-when-compile (require 'cl)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; -;;; Customizable variables -;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar imenu-use-keymap-menu nil - "* Set this to non-nil for using a keymap when making - the mouse menu.") - -(defvar imenu-auto-rescan nil - "* T if we always should rescan the buffers, nil to disable - automatic rescan.") - -(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. - -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 - "*The function to use for sorting the index mouse-menu. - -Affects only the mouse index menu. - -Set this to nil if you don't want any sorting (faster). -The items in the menu are then presented in the order they were found -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 -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 index mouse-menu.") - -(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 "^" - "*The replacement string for spaces in index names. -Used when presenting the index in a completion-buffer to make the -names work as tokens.") - -(defvar 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.") - -(defvar imenu-submenu-name-format "%s..." - "*The format for making a submenu name.") - -;;;###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. - -It is an alist with elements that look like this: (MENU-TITLE -REGEXP INDEX). - -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)) - -The variable is buffer-local.") - -;;;###autoload -(make-variable-buffer-local 'imenu-create-index-pattern) - -;; make sure the default is nil -(setq-default imenu-create-index-pattern nil) - -;;;; Hooks - -(defvar imenu-create-index-function 'imenu-default-create-index-function - "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). - -This function is called within a `save-excursion'. - -The variable is buffer-local.") -(make-variable-buffer-local 'imenu-create-index-function) - -(defvar imenu-prev-index-position-function 'beginning-of-defun - "Function for finding the next index position. - -If `imenu-create-index-function' is set to -`imenu-default-create-index-function', then you must set this variable -to a function that will find the next index, looking backwards in the -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. ") -(make-variable-buffer-local 'imenu-prev-index-position-function) - -(defvar imenu-extract-index-name-function nil - "Function for extracting the index name. - -This function is called after the function pointed out by -`imenu-prev-index-position-function'.") -(make-variable-buffer-local 'imenu-extract-index-name-function) - -;;; -;;; 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))))))) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; -;;;; Some examples of functions utilizing the framework of this -;;;; package. -;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; Return the current/previous sexp and the location of the sexp (it's -;; beginning) without moving the point. -(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) - (cons (buffer-substring beg end) - marker)))) - -;;; -;;; Lisp -;;; - -(defun imenu-example--lisp-extract-index-name () - ;; Example of a candidate for `imenu-extract-index-name-function'. - ;; This will generate a flat index of definitions in a lisp file. - (save-match-data - (and (looking-at "(def") - (condition-case nil - (progn - (down-list 1) - (forward-sexp 2) - (let ((beg (point)) - (end (progn (forward-sexp -1) (point)))) - (buffer-substring beg end))) - (error nil))))) - -(defun imenu-example--create-lisp-index () - ;; Example of a candidate for `imenu-create-index-function'. - ;; It will generate a nested index of definitions. - (let ((index-alist '()) - (index-var-alist '()) - (index-type-alist '()) - (index-unknown-alist '()) - prev-pos) - (goto-char (point-max)) - (imenu-progress-message prev-pos 0) - ;; Search for the function - (while (beginning-of-defun) - (imenu-progress-message prev-pos nil t) - (save-match-data - (and (looking-at "(def") - (save-excursion - (down-list 1) - (cond - ((looking-at "def\\(var\\|const\\)") - (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)) - ((looking-at "def\\(type\\|struct\\|class\\|ine-condition\\)") - (forward-sexp 2) - (if (= (char-after (1- (point))) ?\)) - (progn - (forward-sexp -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) - index-unknown-alist))))))) - (imenu-progress-message prev-pos 100) - (and index-var-alist - (push (cons (imenu-create-submenu-name "Variables") index-var-alist) - index-alist)) - (and index-type-alist - (push (cons (imenu-create-submenu-name "Types") index-type-alist) - index-alist)) - (and index-unknown-alist - (push (cons (imenu-create-submenu-name "Syntax-unknown") index-unknown-alist) - index-alist)) - index-alist)) - -(defvar imenu-generic-lisp-expression - '( - (nil - "^\\s-*(def\\(un\\|subst\\|macro\\|advice\\)\\s-+\\([-A-Za-z0-9+]+\\)" 2) - ("Variables" - "^\\s-*(def\\(var\\|const\\)\\s-+\\([-A-Za-z0-9+]+\\)" 2) - ("Types" - "^\\s-*(def\\(type\\|struct\\|class\\|ine-condition\\)\\s-+\\([-A-Za-z0-9+]+\\)" - 2)) - - "imenu generic expression for Lisp mode in the form -(PATTERN), where PATTERN is a list containing entries of the form -(MENU-TITLE REGEXP INDEX). See `imenu-generic-expression'.") - -;;; -;;; C++ -;;; -;; Example of an imenu-generic-expression -;; -(defvar imenu-generic-c++-expression - (` - ((nil - (, - (concat - "^" ; beginning of line is required - "\\(template[ \t]*<[^>]+>[ \t]*\\)?" ; there may be a "template <...>" - "\\([a-zA-Z0-9_:]+[ \t]+\\)?" ; type specs; there can be no - "\\([a-zA-Z0-9_:]+[ \t]+\\)?" ; more than 3 tokens, right? - - "\\(" ; last type spec including */& - "[a-zA-Z0-9_:]+" - "\\([ \t]*[*&]+[ \t]*\\|[ \t]+\\)" ; either pointer/ref sign or whitespace - "\\)?" ; if there is a last type spec - "\\(" ; name; take that into the imenu entry - "[a-zA-Z0-9_:~]+" ; member function, ctor or dtor... - ; (may not contain * because then - ; "a::operator char*" would become "char*"!) - "\\|" - "\\([a-zA-Z0-9_:~]*::\\)?operator" - "[^a-zA-Z1-9_][^(]*" ; ...or operator - " \\)" - "[ \t]*([^)]*)[ \t\n]*[^ ;]" ; require something other than a ; after - ; the (...) to avoid prototypes. Can't - ; catch cases with () inside the parentheses - ; surrounding the parameters - ; (like "int foo(int a=bar()) {...}" - - )) 6) - ("Class" - (, (concat - "^" ; beginning of line is required - "\\(template[ \t]*<[^>]+>[ \t]*\\)?" ; there may be a "template <...>" - "class[ \t]+" - "\\([a-zA-Z0-9_]+\\)" ; this is the string we want to get - "[ \t]*[:{]" - )) 2) -;; Example of generic expression for finding prototypes, structs, unions, enums. -;; Uncomment if You want to find these too. It will be at bit slower gathering -;; the indexes. -; ("Prototypes" -; (, -; (concat -; "^" ; beginning of line is required -; "\\(template[ \t]*<[^>]+>[ \t]*\\)?" ; there may be a "template <...>" -; "\\([a-zA-Z0-9_:]+[ \t]+\\)?" ; type specs; there can be no -; "\\([a-zA-Z0-9_:]+[ \t]+\\)?" ; more than 3 tokens, right? - -; "\\(" ; last type spec including */& -; "[a-zA-Z0-9_:]+" -; "\\([ \t]*[*&]+[ \t]*\\|[ \t]+\\)" ; either pointer/ref sign or whitespace -; "\\)?" ; if there is a last type spec -; "\\(" ; name; take that into the imenu entry -; "[a-zA-Z0-9_:~]+" ; member function, ctor or dtor... -; ; (may not contain * because then -; ; "a::operator char*" would become "char*"!) -; "\\|" -; "\\([a-zA-Z0-9_:~]*::\\)?operator" -; "[^a-zA-Z1-9_][^(]*" ; ...or operator -; " \\)" -; "[ \t]*([^)]*)[ \t\n]*;" ; require ';' after -; ; the (...) Can't -; ; catch cases with () inside the parentheses -; ; surrounding the parameters -; ; (like "int foo(int a=bar());" -; )) 6) -; ("Struct" -; (, (concat -; "^" ; beginning of line is required -; "\\(static[ \t]+\\)?" ; there may be static or const. -; "\\(const[ \t]+\\)?" -; "struct[ \t]+" -; "\\([a-zA-Z0-9_]+\\)" ; this is the string we want to get -; "[ \t]*[{]" -; )) 3) -; ("Enum" -; (, (concat -; "^" ; beginning of line is required -; "\\(static[ \t]+\\)?" ; there may be static or const. -; "\\(const[ \t]+\\)?" -; "enum[ \t]+" -; "\\([a-zA-Z0-9_]+\\)" ; this is the string we want to get -; "[ \t]*[{]" -; )) 3) -; ("Union" -; (, (concat -; "^" ; beginning of line is required -; "\\(static[ \t]+\\)?" ; there may be static or const. -; "\\(const[ \t]+\\)?" -; "union[ \t]+" -; "\\([a-zA-Z0-9_]+\\)" ; this is the string we want to get -; "[ \t]*[{]" -; )) 3) - )) - "imenu generic expression for C++ mode in the form -(PATTERN), where PATTERN is a list containing entries of the form -(MENU-TITLE REGEXP INDEX). See `imenu-generic-expression'.") - -;;; -;;; C -;;; -;;; -(defvar imenu-generic-c-expression - ;; Use the C++ expression above. - imenu-generic-c++-expression - "imenu generic expression for C mode in the form -(PATTERN), where PATTERN is a list containing entries of the form -(MENU-TITLE REGEXP INDEX). See `imenu-generic-expression'.") - -;; Regular expression to find C functions -(defvar imenu-example--function-name-regexp-c - (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]+\\)?" - "\\([*&]+[ \t]*\\)?" ; pointer - "\\([a-zA-Z0-9_*]+\\)[ \t]*(" ; name - )) - -(defun imenu-example--create-c-index (&optional regexp) - (let ((index-alist '()) - prev-pos char) - (goto-char (point-min)) - (imenu-progress-message prev-pos 0) - ;; Search for the function - (save-match-data - (while (re-search-forward - (or regexp imenu-example--function-name-regexp-c) - nil t) - (imenu-progress-message prev-pos) - (backward-up-list 1) - (save-excursion - (goto-char (scan-sexps (point) 1)) - (setq char (following-char))) - ;; Skip this function name if it is a prototype declaration. - (if (not (eq char ?\;)) - (push (imenu-example--name-and-position) index-alist)))) - (imenu-progress-message prev-pos 100) - (nreverse index-alist))) - - -;; -;; Ada -;; -;; Written by Christian Egli -;; -(defvar imenu-generic-ada-expression - '((nil "^\\s-*\\(procedure\\|function\\)\\s-+\\([A-Za-z0-9_]+\\)" 2) - ("Type Defs" "^\\s-*\\(sub\\)?type\\s-+\\([A-Za-z0-9_]+\\)" 2)) - - "imenu generic expression for Ada mode in the form -(PATTERN), where PATTERN is a list containing entries of the form -(MENU-TITLE REGEXP INDEX). See `imenu-generic-expression'.") - -;;; -;;; TexInfo -;;; -;; Written by Wolfgang Bangerth -;; -;; -(defvar imenu-generic-texinfo-expression - '((nil "^@node[ \t]+\\([^,\n]*\\)" 1) - ("Chapters" "^@chapter[ \t]+\\(.*\\)$" 1)) - - "imenu generic expression for TexInfo mode in the form -(PATTERN), where PATTERN is a list containing entries of the form -(MENU-TITLE REGEXP INDEX). See `imenu-generic-expression'. - -To overide this example, Either set 'imenu-generic-expression -or 'imenu-create-index-function") - -;;; -;;; LaTex -;;; -;; Written by Wolfgang Bangerth -;; -;; -(defvar imenu-generic-latex-expression - '( - ("Part" "\\\\part{\\([^}]*\\)}" 1) - ("Chapter" "\\\\chapter{\\([^}]*\\)}" 1) - ("Section" "\\\\[a-zA-Z]*section{\\([^}]*\\)}" 1) - ;; i put numbers like 3.15 before my - ;; \begin{equation}'s which tell me - ;; the number the equation will get when - ;; being printed. - ("Equations" "%[ \t]*\\([0-9]+\\.[0-9]+\\)[,;]?[ \t]?" 1)) - - "imenu generic expression for LaTex mode in the form -(PATTERN), where PATTERN is a list containing entries of the form -(MENU-TITLE REGEXP INDEX). See `imenu-generic-expression'.") - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; -;;; Internal variables -;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; The item to use in the index for rescanning the buffer. -(defconst imenu--rescan-item '("*Rescan*" . -99)) - -;; The latest buffer index. -;; Buffer local. -(defvar imenu--index-alist nil) -(make-variable-buffer-local 'imenu--index-alist) - -;; History list for 'jump-to-function-in-buffer'. -;; Buffer local. -(defvar imenu--history-list nil) -(make-variable-buffer-local 'imenu--history-list) - -(defvar imenu--scanning-method-alist - '((emacs-lisp-mode imenu-generic-lisp-expression) - (lisp-mode imenu-example--create-lisp-index) - (c++-mode imenu-generic-c++-expression) - (c-mode imenu-generic-c-expression) - (latex-mode imenu-generic-latex-expression) - (texinfo-mode imenu-generic-texinfo-expression) - (ada-mode imenu-generic-ada-expression)) - - "Alist of major mode and imenu scanning methods. - -Each item should be a list of the form: (MAJOR-MODE -IMENU-SCANNING-METHOD) where both MAJOR-MODE and IMENU-SCANNING-METHOD -are symbols. If IMENU-SCANNING-METHOD is a function then it is called -to create an index. If it is a `pattern' (See `imenu-generic-expression') -it is passed to imenu--generic-function to create an index.") - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; -;;; Internal support functions -;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;;; -;;; Sort function -;;; Sorts the items depending on their index name. -;;; An item look like (NAME . POSITION). -;;; -(defun imenu--sort-by-name (item1 item2) - (string-lessp (car item1) (car 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 - ;; If REVERSE is non-nil then the beginning is 100 and the end is 0. - (let ((pos (point)) - (total (buffer-size))) - (and reverse (setq pos (- total pos))) - (if (> total 50000) - ;; Avoid overflow from multiplying by 100! - (/ (1- pos) (max (/ total 100) 1)) - (/ (* 100 (1- pos)) (max total 1))))) - -;;; -;;; Function for suporting general looking submenu names. -;;; Uses `imenu-submenu-name-format' for creating the name. -;;; NAME is the base of the new submenu name. -;;; -(defun imenu-create-submenu-name (name) - (format imenu-submenu-name-format name)) - -;; Split LIST into sublists of max length N. -;; Example (imenu--split '(1 2 3 4 5 6 7 8) 3)-> '((1 2 3) (4 5 6) (7 8)) -(defun imenu--split (list n) - (let ((remain list) - (result '()) - (sublist '()) - (i 0)) - (while remain - (push (pop remain) sublist) - (incf i) - (and (= i n) - ;; We have finished a sublist - (progn (push (nreverse sublist) result) - (setq i 0) - (setq sublist '())))) - ;; There might be a sublist (if the length of LIST mod n is != 0) - ;; that has to be added to the result list. - (and sublist - (push (nreverse sublist) result)) - (nreverse result))) - -;;; -;;; Split a menu in to several menus. -;;; -(defun imenu--split-menu (menulist title) - (cons "Index menu" - (mapcar - (function - (lambda (menu) - (cons (format "(%s)" title) menu))) - (imenu--split menulist imenu-max-items)))) - -;;; -;;; Find all items in this buffer that should be in the index. -;;; Returns an alist on the form -;;; ((NAME . POSITION) (NAME . POSITION) ...) -;;; - -(defun imenu--make-index-alist () - ;; Create a list for this buffer only when needed. - (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)))) - (or imenu--index-alist - (error "No items suitable for an index found in this buffer.")) - ;; Add a rescan option to the index. - (cons imenu--rescan-item imenu--index-alist)) -;;; -;;; Find all markers in alist and makes -;;; them point nowhere. -;;; -(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)) - (and alist - (mapcar - (function - (lambda (item) - (cond - ((markerp (cdr item)) - (set-marker (cdr item) nil)) - ((consp (cdr item)) - (imenu--cleanup (cdr item)))))) - alist) - t)) - -(defun imenu--create-keymap-2 (alist counter) - (let ((map nil)) - (mapcar - (function - (lambda (item) - (cond - ((listp (cdr item)) - (append (list (incf counter) (car item) 'keymap (car item)) - (imenu--create-keymap-2 (cdr item) (+ counter 10)))) - (t - (let ((end (cons '(nil) t))) - (cons (car item) - (cons (car item) end)))) - ))) - alist))) - -(defun imenu--create-keymap-1 (title alist) - (append (list 'keymap title) (imenu--create-keymap-2 alist 0))) - - -(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) - 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)))) - res)) - -(defun imenu-default-create-index-function () - "*Wrapper for index searching functions. - -Moves point to end of buffer and then repeatedly calls -`imenu-prev-index-position-function' and `imenu-extract-index-name-function'. -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)) - (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) - (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))) - (imenu-progress-message prev-pos 100 t) - index-alist)) - ;; Use generic expression if possible. - ((and imenu-generic-expression) - (imenu--generic-function imenu-generic-expression)) - ;; Use supplied example functions or expressions - ((assq major-mode imenu--scanning-method-alist) - (let ((method (cadr (assq major-mode imenu--scanning-method-alist)))) - ;; is it a function? - (if (fboundp method) - ;; ... then call it - (funcall method) - ;; ...otherwise pass the pattern to imenu--generic-function - (imenu--generic-function (eval method))))) - (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)) - -;;; -;;; Generic index gathering function. -;;; - -(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). - -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))' - -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\)." - - (let ((index-alist (list 'dummy)) - (found nil) - (global-regexp - (concat "\\(" - (mapconcat - (function (lambda (pattern) (identity (cadr pattern)))) - patterns "\\)\\|\\(") - "\\)")) - prev-pos) - - (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))) - (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 beg end) beg) - (cdr - (or (if (not (stringp menu-title)) index-alist) - (assoc - (imenu-create-submenu-name menu-title) - index-alist) - (car (push - (cons - (imenu-create-submenu-name menu-title) - '()) - index-alist)))))))))) - patterns)))) - (imenu-progress-message prev-pos 100 t) - (delete 'dummy index-alist))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; -;;; The main functions for this package! -;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(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." - ;; 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 (listp (cdr 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. - -INDEX-ALIST is the buffer index and EVENT is a mouse event. - -Returns t for rescan and otherwise a position number." - (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) - (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 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))))) - -(defun imenu-choose-buffer-index (&optional prompt alist) - "Let the user select from a buffer index and return the chosen index. - -If the user originally activated this function with the mouse, a mouse -menu is used. Otherwise a completion buffer is used and the user is -prompted with PROMPT. - -If you call this function with index alist ALIST, then it lets the user -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 -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)." - (let (index-alist - (mouse-triggered (listp last-nonmenu-event)) - (result t) ) - ;; If selected by mouse, see to that the window where the mouse is - ;; really is selected. - (and mouse-triggered - (not (equal last-nonmenu-event '(menu-bar))) - (let ((window (posn-window (event-start last-nonmenu-event)))) - (or (framep window) (null window) (select-window window)))) - ;; Create a list for this buffer only when needed. - (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)) - (imenu--mouse-menu index-alist last-nonmenu-event) - (imenu--completion-buffer index-alist prompt))) - (and (eq result t) - (imenu--cleanup) - (setq imenu--index-alist nil))) - result)) - -;;;###autoload -(defun imenu-add-to-menubar (name) - "Adds an \"imenu\" entry to the menubar for the -current local keymap. -NAME is the string naming the menu to be added. -See 'imenu' for more information." - (interactive "sMenu name: ") - (and window-system - (define-key (current-local-map) [menu-bar index] - (cons name 'imenu)))) - -;;;###autoload -(defun imenu () - "Jump to a place in the buffer chosen using a buffer menu or mouse menu. -See `imenu-choose-buffer-index' for more information." - (interactive) - (let ((index-item (save-restriction - (widen) - (imenu-choose-buffer-index)))) - (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)))))))) - -(provide 'imenu) - -;;; imenu.el ends here +;;; imenu.el --- framework for mode-specific buffer indexes + +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 2003 Free Software Foundation, Inc. + +;; Author: Ake Stenhoff +;; Lars Lindberg +;; Maintainer: FSF +;; Created: 8 Feb 1994 +;; Keywords: tools convenience + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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: + +;; Purpose of this package: +;; To present a framework for mode-specific buffer indexes. +;; 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 +;; then presented to the user, who can choose from this index. +;; +;; The package comes with a set of example functions for how to +;; utilize this package. + +;; There are *examples* for index gathering functions/regular +;; expressions for C/C++ and Lisp/Emacs Lisp but it is easy to +;; customize for other modes. A function for jumping to the chosen +;; index position is also supplied. + +;;; 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 +;; [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 +;; [david] - David M. Smith dsmith@stats.adelaide.edu.au +;; [christian] - Christian Egli Christian.Egli@hcsd.hac.com +;; [karl] - Karl Fogel kfogel@floss.life.uiuc.edu + +;;; Code: + +(eval-when-compile (require 'cl)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Customizable variables +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(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 "21.4") + +(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))) + +(defcustom imenu-eager-completion-buffer + (not (eq imenu-always-use-completion-buffer-p 'never)) + "If non-nil, eagerly popup the completion buffer." + :type 'boolean) + +(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) + +;;;###autoload +(defcustom imenu-sort-function nil + "*The function to use for sorting the index mouse-menu. + +Affects only the mouse index menu. + +Set this to nil if you don't want any sorting (faster). +The items in the menu are then presented in the order they were found +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 +element should come before the second. The arguments are cons cells; +\(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) + +(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." + :type '(choice string (const nil)) + :group 'imenu) + +(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." + :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--generic-function' +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 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. + +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. + +The variable is buffer-local. + +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. + +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. + +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 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'. + +The variable is buffer-local.") +;;;###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. + +If `imenu-create-index-function' is set to +`imenu-default-create-index-function', then you must set this variable +to a function that will find the next index, looking backwards in the +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. + +This variable is local in all buffers.") +;;;###autoload +(make-variable-buffer-local 'imenu-prev-index-position-function) + +;;;###autoload +(defvar imenu-extract-index-name-function nil + "Function for extracting the index item name, given a position. + +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. + +This variable is local in all buffers.") +;;;###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\". + +This variable is local in all buffers.") +;;;###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 (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. +(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)))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Some examples of functions utilizing the framework of this +;;;; package. +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Return the current/previous sexp and the location of the sexp (its +;; beginning) without moving the point. +(defun imenu-example--name-and-position () + (save-excursion + (forward-sexp -1) + ;; [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) + beg)))) + +;;; +;;; Lisp +;;; + +(defun imenu-example--lisp-extract-index-name () + ;; Example of a candidate for `imenu-extract-index-name-function'. + ;; This will generate a flat index of definitions in a lisp file. + (save-match-data + (and (looking-at "(def") + (condition-case nil + (progn + (down-list 1) + (forward-sexp 2) + (let ((beg (point)) + (end (progn (forward-sexp -1) (point)))) + (buffer-substring beg end))) + (error nil))))) + +(defun imenu-example--create-lisp-index () + ;; Example of a candidate for `imenu-create-index-function'. + ;; It will generate a nested index of definitions. + (let ((index-alist '()) + (index-var-alist '()) + (index-type-alist '()) + (index-unknown-alist '()) + prev-pos) + (goto-char (point-max)) + (imenu-progress-message prev-pos 0) + ;; Search for the function + (while (beginning-of-defun) + (imenu-progress-message prev-pos nil t) + (save-match-data + (and (looking-at "(def") + (save-excursion + (down-list 1) + (cond + ((looking-at "def\\(var\\|const\\)") + (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)) + ((looking-at "def\\(type\\|struct\\|class\\|ine-condition\\)") + (forward-sexp 2) + (if (= (char-after (1- (point))) ?\)) + (progn + (forward-sexp -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) + index-unknown-alist))))))) + (imenu-progress-message prev-pos 100) + (and index-var-alist + (push (cons "Variables" index-var-alist) + index-alist)) + (and index-type-alist + (push (cons "Types" index-type-alist) + index-alist)) + (and index-unknown-alist + (push (cons "Syntax-unknown" index-unknown-alist) + index-alist)) + index-alist)) + +;; Regular expression to find C functions +(defvar imenu-example--function-name-regexp-c + (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]+\\)?" + "\\([*&]+[ \t]*\\)?" ; pointer + "\\([a-zA-Z0-9_*]+\\)[ \t]*(" ; name + )) + +(defun imenu-example--create-c-index (&optional regexp) + (let ((index-alist '()) + prev-pos char) + (goto-char (point-min)) + (imenu-progress-message prev-pos 0) + ;; Search for the function + (save-match-data + (while (re-search-forward + (or regexp imenu-example--function-name-regexp-c) + nil t) + (imenu-progress-message prev-pos) + (backward-up-list 1) + (save-excursion + (goto-char (scan-sexps (point) 1)) + (setq char (following-char))) + ;; Skip this function name if it is a prototype declaration. + (if (not (eq char ?\;)) + (push (imenu-example--name-and-position) index-alist)))) + (imenu-progress-message prev-pos 100) + (nreverse index-alist))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Internal variables +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The item to use in the index for rescanning the buffer. +(defconst imenu--rescan-item '("*Rescan*" . -99)) + +;; The latest buffer index. +;; Buffer local. +(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). + +This variable is local in all buffers, once set.") + +(make-variable-buffer-local 'imenu--index-alist) + +(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'. +;; Making this buffer local caused it not to work! +(defvar imenu--history-list nil) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Internal support functions +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; +;;; Sort function +;;; Sorts the items depending on their index name. +;;; 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 + ;; If REVERSE is non-nil then the beginning is 100 and the end is 0. + (let ((pos (point)) + (total (buffer-size))) + (and reverse (setq pos (- total pos))) + (if (> total 50000) + ;; Avoid overflow from multiplying by 100! + (/ (1- pos) (max (/ total 100) 1)) + (/ (* 100 (1- pos)) (max total 1))))) + +;; Split LIST into sublists of max length N. +;; Example (imenu--split '(1 2 3 4 5 6 7 8) 3)-> '((1 2 3) (4 5 6) (7 8)) +(defun imenu--split (list n) + (let ((remain list) + (result '()) + (sublist '()) + (i 0)) + (while remain + (push (pop remain) sublist) + (incf i) + (and (= i n) + ;; We have finished a sublist + (progn (push (nreverse sublist) result) + (setq i 0) + (setq sublist '())))) + ;; There might be a sublist (if the length of LIST mod n is != 0) + ;; that has to be added to the result list. + (and sublist + (push (nreverse sublist) result)) + (nreverse result))) + +;;; 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) + (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) + (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 menulist imenu-sort-function))) + (if (> (length 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))) + 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))) + (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 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. + +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; 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) + ;; 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 + (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 (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) + tail (cdr 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 + ;; (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))) + ((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. + +Moves point to end of buffer and then repeatedly calls +`imenu-prev-index-position-function' and `imenu-extract-index-name-function'. +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 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) + (imenu-progress-message prev-pos nil t) + (save-excursion + (setq name (funcall imenu-extract-index-name-function))) + (and (stringp name) + ;; [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 "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) + +;; Originally "Built on some ideas that Erik Naggum +;; once posted to comp.emacs" but since substantially re-written. +(defun imenu--generic-function (patterns) + "Return an index of the current buffer as an alist. + +PATTERNS is an alist with elements that look like this: + (MENU-TITLE REGEXP INDEX). + +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. + +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 +PATTERNS." + + (let ((index-alist (list 'dummy)) + prev-pos beg + (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) + (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))) + ;; Go backwards for convenience of adding items in order. + (goto-char (point-max)) + (while (re-search-backward regexp nil t) + (imenu-progress-message prev-pos nil t) + (setq beg (match-beginning index)) + ;; 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)))))))) + (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))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; The main functions for this package! +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; 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." + ;; Create a list for this buffer only when needed. + (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 ?\ (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))) + (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))))) + +(defun imenu--mouse-menu (index-alist event &optional title) + "Let the user select from a buffer index from a mouse menu. + +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)))) + (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. + +If the user originally activated this function with the mouse, a mouse +menu is used. Otherwise a completion buffer is used and the user is +prompted with PROMPT. + +If you call this function with index alist ALIST, then it lets the user +select from ALIST. + +With no index alist ALIST, it calls `imenu--make-index-alist' to +create the index alist. + +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)) + ;; If selected by mouse, see to that the window where the mouse is + ;; really is selected. + (and mouse-triggered + (not (equal last-nonmenu-event '(menu-bar))) + (let ((window (posn-window (event-start last-nonmenu-event)))) + (or (framep window) (null window) (select-window window)))) + ;; Create a list for this buffer only when needed. + (while (eq result t) + (setq index-alist (if alist alist (imenu--make-index-alist))) + (setq result + (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) + (imenu--cleanup) + (setq imenu--index-alist nil))) + result)) + +;;;###autoload +(defun imenu-add-to-menubar (name) + "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: ") + (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'. +This value becomes local in every buffer when it is set.") +(make-variable-buffer-local 'imenu-menubar-modified-tick) + +(defun imenu-update-menubar () + (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 imenu--rescan-item) + (progn + (imenu--cleanup) + (setq imenu--index-alist nil) + (imenu-update-menubar) + t) + (imenu item) + nil)) + +(defun imenu-default-goto-function (name position &optional rest) + "Move the point 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. +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)))) + (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) + +;;; imenu.el ends here