Mercurial > hg > xemacs-beta
diff lisp/modes/imenu.el @ 2:ac2d302a0011 r19-15b2
Import from CVS: tag r19-15b2
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:46:35 +0200 |
parents | |
children | b82b59fe008d |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/modes/imenu.el Mon Aug 13 08:46:35 2007 +0200 @@ -0,0 +1,869 @@ +;;; imenu.el --- Framework for mode-specific buffer indexes. + +;; Copyright (C) 1994, 1995, 1996 Free Software Foundation, Inc. + +;; Author: Ake Stenhoff <etxaksf@aom.ericsson.se> +;; Lars Lindberg <lli@sypro.cap.se> +;; Created: 8 Feb 1994 +;; Keywords: tools + +;; This file is part of XEmacs. + +;; XEmacs 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. + +;; XEmacs 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 XEmacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Synched up with: FSF 19.34. + +;;; 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-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. + +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 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 "^" + "*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.") + +;;;###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-generic-expression) + +;;;; 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 (its +;; 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 "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) +(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) +(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 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))))) + +;; 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) + (if (> (length menulist) imenu-max-items) + (let ((count 0)) + (cons title + (mapcar + (function + (lambda (menu) + (cons (format "(%s-%d)" title (setq count (1+ count))) + menu))) + (imenu--split menulist imenu-max-items)))) + (cons title menulist))) + +;;; 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)) + +;;; +;;; 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 (&optional noerror) + ;; 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 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. +;;; +(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 &optional commands) + (let ((map nil)) + (mapcar + (function + (lambda (item) + (cond + ((listp (cdr item)) + (append (list (setq counter (1+ counter)) + (car item) 'keymap (car item)) + (imenu--create-keymap-2 (cdr item) (+ counter 10) commands))) + (t + (let ((end (if commands `(lambda () (interactive) + (imenu--menubar-select ',item)) + (cons '(nil) t)))) + (cons (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--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)) + (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 <erik@naggum.no> 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-no-properties beg end) beg) + (cdr + (or (assoc menu-title index-alist) + (car (push + (cons menu-title '()) + index-alist)))))))))) + patterns)))) + (imenu-progress-message prev-pos 100 t) + (let ((main-element (assq nil index-alist))) + (nconc (delq main-element (delq 'dummy index-alist)) main-element)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; 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." + (setq index-alist (imenu--split-submenus index-alist)) + (let* ((menu (imenu--split-menu + (if imenu-sort-function + (sort + (let ((res nil) + (oldlist index-alist)) + ;; Copy list method from the cl package `copy-list' + (while (consp oldlist) (push (pop oldlist) res)) + (prog1 (nreverse res) (setcdr res oldlist))) + imenu-sort-function) + index-alist) + (or title (buffer-name)))) + position) + (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)) + (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 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)) + +(defvar imenu-buffer-menubar nil) + +(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 + (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) + (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))))))) + +(defun imenu--menubar-select (item) + "Use Imenu to select the function or variable named in this menu item." + (if (equal item '("*Rescan*" . -99)) + (progn + (imenu--cleanup) + (setq imenu--index-alist nil) + (imenu-update-menubar)) + (imenu item))) + +;;;###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)))) + ;; Convert a string to an alist element. + (if (stringp index-item) + (setq index-item (assoc index-item (imenu--make-index-alist)))) + (and index-item + (progn + (push-mark) + (cond + ((markerp (cdr index-item)) + (if (or ( > (marker-position (cdr index-item)) (point-min)) + ( < (marker-position (cdr index-item)) (point-max))) + ;; widen if outside narrowing + (widen)) + (goto-char (marker-position (cdr index-item)))) + (t + (if (or ( > (cdr index-item) (point-min)) + ( < (cdr index-item) (point-max))) + ;; widen if outside narrowing + (widen)) + (goto-char (cdr index-item))))))) + +(provide 'imenu) + +;;; imenu.el ends here +