(compile-reinitialize-errors): Rename first arg from ARGP to REPARSE.
[bpt/emacs.git] / lisp / imenu.el
CommitLineData
0a688fd0
RS
1;;; imenu.el --- Framework for mode-specific buffer indexes.
2
3;; Copyright (C) 1994 Free Software Foundation, Inc.
4
5;; Author: Ake Stenhoff <etxaksf@aom.ericsson.se>
6;; Lars Lindberg <lli@sypro.cap.se>
7;; Created: 8 Feb 1994
8;; Version: 1.4
9;; Keywords: tools
10;;
11;; This program is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15;;
16;; This program is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20;;
21;; You should have received a copy of the GNU General Public License
22;; along with this program; if not, write to the Free Software
23;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24
25;;; Commentary:
26;;
27;; Purpose of this package:
28;; To present a framework for mode-specific buffer indexes.
29;; A buffer index is an alist of names and buffer positions.
30;; For instance all functions in a C-file and their positions.
31;;
32;; How it works:
33
34;; A mode-specific function is called to generate the index. It is
35;; then presented to the user, who can choose from this index.
36;;
37;; The package comes with a set of example functions for how to
38;; utilize this package.
39
40;; There are *examples* for index gathering functions for C/C++ and
41;; Lisp/Emacs Lisp but it is easy to customize for other modes. A
42;; function for jumping to the chosen index position is also
43;; supplied.
44;;
45;; Installation:
46;; Put this file in your load-path and insert the following in .emacs
47;;
48;; (autoload 'imenu-choose-buffer-index "imenu" "Menu of buffer index." t)
49;; (autoload 'goto-index-pos "imenu" "Goto buffer index position." t)
50;; (define-key global-map "\C-cj" 'goto-index-pos) ;; Or some other key
51;; (cond (window-system
52;; (define-key global-map [S-down-mouse-3] 'goto-index-pos))
53
54;;; Change Log:
55;; v1.4 Feb 18 1994ine-key global-map [S-down-mouse-3] 'goto-index-pos))
56
57;;; Change Log:
58;; v1.4 Feb 18 1994 Ake Stenhoff
59;; Added 'imenu-create-submenu-name' for creating a submenu name.
60;; This is for getting a general look of submenu names.
61;; Added variable 'imenu-submenu-name-format' used by
62;; 'imenu-create-submenu-name'.
63;; v1.3 Feb 17 1994 Lars Lindberg
64;; Added 'imenu--flatten-index-alist' for flatten nexted index
65;; alists.
66;; New examples for lisp mode that utilizes the features better.
67;; Added the variable 'imenu-space-replacement'.
68;; The completion-buffer version of the index menu now replaces
69;; spaces in the index-names to make tokens of them.
70;; v1.2 Feb 14 1994 Ake Stenhoff & Lars Lindberg
71;; Now handles nested index lists.
72;; v1.1 Feb 9 1994 Ake Stenhoff & Lars Lindberg
73;; Better comments (?).
74;; v1.0 Feb 8 1994 Ake Stenhoff & Lars Lindberg
75;; Based on func-menu.el 3.5.
76
77;;; Code
78(require 'cl)
79
80;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
81;;;
82;;; Customizable variables
83;;;
84;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
85
86(defvar imenu-always-use-completion-buffer-p nil
87 "*Set this to non-nil for displaying the index in a completion buffer.
88
89Non-nil means always display the index in a completion buffer.
90Nil means display the index as a mouse menu when the mouse was
91used to trigger 'goto-index-pos'.")
92
93(defvar imenu-sort-function nil
94 "*The function to use for sorting the index mouse-menu.
95
96Affects only the mouse index menu.
97
98Set this to nil if you don't want any sorting (faster).
99The items in the menu are then presented in the order they were found
100in the buffer.
101
102Set it to 'imenu--sort-by-name if you want alphabetic sorting.
103
104The function should take two arguments and return T if the first
105element should come before the second. The arguments are cons cells;
106(NAME . POSITION). Look at 'imenu--sort-by-name' for an example.")
107
108(defvar imenu-max-items 25
109 "*Maximum number of elements in an index mouse-menu.")
110
111(defvar imenu-scanning-message "Scanning buffer for index. (%3d%%)"
112 "*Progress message during the index scanning of the buffer.
113If non NIL, user gets a message during the scanning of the buffer
114
115Relevant only if the mode-specific function that creates the buffer
116index use 'imenu-progress-message'.")
117
118(defvar imenu-space-replacement "^"
119 "*The replacement string for spaces in index names.
120Used when presenting the index in a completion-buffer to make the
121names work as tokens.")
122
123(defvar imenu-level-separator ":"
124 "*The separator between index names of different levels.
125Used for making mouse-menu titles and for flattening nested indexes
126with name concatenation.")
127
128(defvar imenu-submenu-name-format "%s..."
129 "*The format for making a submenu name.")
130
131;;;; Hooks
132
133(defvar imenu-create-index-function 'imenu-default-create-index-function
134 "The function to use for creating a buffer index.
135
136It should be a function that takes no arguments and returns an index
137of the current buffer as an alist. The elements in the alist look
138like: (INDEX-NAME . INDEX-POSITION). You may also nest index list like
139(INDEX-NAME . INDEX-ALIST).
140
141This function is called within a 'save-excursion'.
142
143The variable is buffer-local.")
144(make-variable-buffer-local 'imenu-create-index-function)
145
146(defvar prev-index-position-function 'beginning-of-defun
147 "Function for finding the next index position.
148
149If 'imenu-create-index-function' is set to
150'imenu-default-create-index-function, then you must set this variable
151to a function that will find the next index, looking backwards in the
152file.
153
154The function should leave point at the place to be connected to the
155index and it should return nil when it doesn't find another index. ")
156(make-variable-buffer-local 'prev-index-position-function)
157
158(defvar extract-index-name-function nil
159 "Function for extracting the index name.
160
161This function is called after the function pointed out by
162'prev-index-position-functioname.
163
164This function is called after the function pointed out by
165'prev-index-position-function'.")
166(make-variable-buffer-local 'extract-index-name-function)
167
168;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
169;;;
170;;; Internal variables
171;;;
172;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
173
174;; The item to use in the index for rescanning the buffer.
175(defconst imenu--rescan-item '("*Rescan*" . -99))
176
177;; The latest buffer index.
178;; Buffer local.
179(defvar imenu--index-alist nil)
180(make-variable-buffer-local 'imenu--index-alist)
181
182;; History list for 'jump-to-function-in-buffer'.
183;; Buffer local.
184(defvar imenu--history-list nil)
185(make-variable-buffer-local 'imenu--history-list)
186
187;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
188;;;
189;;; Internal support functions
190;;;
191;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
192
193;;;
194;;; Sort function
195;;; Sorts the items depending on their index name.
196;;; An item look like (NAME . POSITION).
197;;;
198(defun imenu--sort-by-name (item1 item2)
199 (string-lessp (car item1) (car item2)))
200
201(defun imenu--relative-position (&optional reverse)
202 ;; Support function to calculate relative position in buffer
203 ;; Beginning of buffer is 0 and end of buffer is 100
204 ;; If REVERSE is non-nil then the beginning is 100 and the end is 0.
205 (let ((pos (point))
206 (total (buffer-size)))
207 (and reverse (setq pos (- total pos)))
208 (if (> total 50000)
209 ;; Avoid overflow from multiplying by 100!
210 (/ (1- pos) (max (/ total 100) 1))
211 (/ (* 100 (1- pos)) (max total 1)))))
212
213;;;
214;;; Macro to display a progress message. This will probably be used
215;;; in a tight loop, that is why we use a macro.
216;;; RELPOS is the relative position to display.
217;;; If RELPOS is nil, then the relative position in the buffer
218;;; is calculated.
219(defmacro imenu-progress-message (&optional relpos reverse)
220 (` (and
221 imenu-scanning-message
222 (message imenu-scanning-message
223 (, (if relpos
224 relpos
225 (` (imenu--relative-position (, reverse)))))))))
226
227;;;
228;;; Function for suporting general looking submenu names.
229;;; Uses 'imenu-submenu-name-format' for creating the name.
230;;; NAME is the base of the new submenu name.
231;;;
232(defun imenu-create-submenu-name (name)
233 (format imenu-submenu-name-format name))
234
235;; Split LIST into sublists of max length N.
236;; Example (imenu--split '(1 2 3 4 5 6 7 8) 3)-> '((1 2 3) (4 5 6) (7 8))
237(defun imenu--split (list n)
238 (let ((remain list)
239 (result '())
240 (sublist '())
241 (i 0))
242 (while remain
243 (push (pop remain) sublist)
244 (incf i)
245 (and (= i n)
246 ;; We have finished a sublist
247 (progn (push (nreverse sublist) result)
248 (setq i 0)
249 (setq sublist '()))))
250 ;; There might be a sublist (if the length of LIST mod n is != 0)
251 ;; that has to be added to the result list.
252 (and sublist
253 (push (nreverse sublist) result))
254 (nreverse result)))
255
256;;;
257;;; Split a menu in to several menus.
258;;;
259(defun imenu--split-menu (menulist title)
260 (cons "Function menus"
261 (mapcar
262 (function
263 (lambda (menu)
264 (cons (format "(%s)" title) menu)))
265 (imenu--split menulist imenu-max-items))))
266
267;;;
268;;; Find all items in this buffer that should be in the index.
269;;; Returns an alist on the form
270;;; ((NAME . POSITION) (NAME . POSITION) ...)
271;;;
272
273(defun imenu--make-index-alist ()
274 ;; Create a list for this buffer only when needed.
275 (or imenu--index-alist
276 ;; Get the index
277 (setq imenu--index-alist
278 (save-excursion
279 (funcall imenu-create-index-function))))
280 (or imenu--index-alist
281 (error "No items suitable for an index found in this buffer."))
282 ;; Add a rescan option to the index.
283 (cons imenu--rescan-item imenu--index-alist))
284
285(defun imenu-default-create-index-function ()
286 "*Wrapper for index searching functions.
287
288Moves point to end of buffer and then repeatedly calls
289'prev-index-position-function' and 'extract-index-name-function'.
290Their results are gathered into an index aliition-function' and 'extract-index-name-function'.
291Their results are gathered into an index alist."
292
293 (or (and (fboundp prev-index-position-function)
294 (fboundp extract-index-name-function))
295 (error "The mode \"%s\" does not take full advantage of imenu.el yet."
296 mode-name))
297 (let ((index-alist '())
298 name)
299 (goto-char (point-max))
300 (imenu-progress-message 0 t)
301 ;; Search for the function
302 (while (funcall prev-index-position-function)
303 (imenu-progress-message nil t)
304 (save-excursion
305 (setq name (funcall extract-index-name-function)))
306 (and (stringp name)
307 (push (cons name (point)) index-alist)))
308 (imenu-progress-message 100 t)
309 index-alist))
310
311(defun imenu--replace-spaces (name replacement)
312 ;; Replace all spaces in NAME with REPLACEMENT.
313 ;; That second argument should be a string.
314 (mapconcat
315 (function
316 (lambda (ch)
317 (if (char-equal ch ?\ )
318 replacement
319 (char-to-string ch))))
320 name
321 ""))
322
323(defun imenu--flatten-index-alist (index-alist &optional concat-names prefix)
324 ;; Takes a nested INDEX-ALIST and returns a flat index alist.
325 ;; If optional CONCAT-NAMES is non-nil, then a nested index has its
326 ;; name and a space concatenated to the names of the children.
327 ;; Third argument PREFIX is for internal use only.
328 (mapcan
329 (function
330 (lambda (item)
331 (let* ((name (car item))
332 (pos (cdr item))
333 (new-prefix (and concat-names
334 (if prefix
335 (concat prefix imenu-level-separator name)
336 name))))
337 (cond
338 ((numberp pos)
339 (list (cons new-prefix pos)))
340 (t
341 (imenu--flatten-index-alist pos new-prefix))))))
342 index-alist))
343
344;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
345;;;
346;;; The main functions for this package!
347;;;
348;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
349
350(defun imenu--completion-buffer (index-alist &optional prompt)
351 "Let the user select from INDEX-ALIST in a completion buffer with PROMPT.
352
353Returns t for rescan and otherwise a position number."
354 ;; Create a list for this buffer only when needed.
355 (let (name choice
356 (prepared-index-alist
357 (mapcar
358 (function
359 (lambda (item)
360 (cons (imenu--replace-spaces (car item) imenu-space-replacement)
361 (cdr item))))
362 index-alist)))
363 (save-window-excursion
364 ;; Display the completion buffer
365 (with-output-to-temp-buffer "*Completions*"
366 (display-completion-list
367 (all-completions "" prepared-index-alist )))
368 ;; Make a completion question
369 (setq name (completing-read (or prompt "Index item: ")
370 prepared-index-alist
371 nil t nil 'imenu--history-list)))
372 (cond
373 ((not (stringp name))
374 nil)
375 ((string= name (car imenu--rescan-item))
376 t)
377 (t
378 (setq choice (assoc name prepared-index-alist))
379 (cond
380 ((listp (cdr choice))
381 (imenu--completion-buffer (cdr choice) prompt))
382 (t
383 choice))))))
384
385(defun imenu--mouse-menu (index-alist event &optional title)
386 "Let the user select from a buffer index from a mouse menu.
387
388INDEX-ALIST is the buffer index and EVENT is a mouse event.
389
390Returns t for rescan and otherwise a position number."
391 (let* ((menu (imenu--split-menu
392 (if imenu-sort-function
393 (sort (copy-list index-alist) imenu-sort-function)
394 index-alist)
395 (or title (buffer-name))))
396 position)
397 (setq position (x-popup-menu event menu))
398 (cond
399 ((eq position nil)
400 position)
401 ((not (numberp position))
402 (imenu--mouse-menu position event
403 (if title
404 (concat title imenu-level-separator
405 (car (rassq position index-alist)))
406 (car (rassq position index-alist)))))
407 ((= position (cdr imenu--rescan-item))
408 t)
409 (t
410 (rassq position index-alist)))))
411
412(defun imenu-choose-buffer-index (&optional prompt)
413 "Let the user select from a buffer index and return the chosen index.
414
415If the user originally activated this function with the mouse, a mouse
416menu is used. Otherwise f the user originally activated this function with the mouse, a mouse
417menu is used. Otherwise a completion buffer is used and the user is
418prompted with PROMPT.
419
420If 'imenu-always-use-completion-buffer-p' is non-nil, then the
421completion buffer is always used, no matter if the mouse was used or
422not.
423
424The returned value is on the form (INDEX-NAME . INDEX-POSITION)."
425 (let (index-alist
426 (mouse-triggered (listp last-command-event))
427 (result t) )
428 ;; If selected by mouse, see to that the window where the mouse is
429 ;; really is selected.
430 (and mouse-triggered
431 (let ((window (posn-window (event-start last-command-event))))
432 (or (framep window) (select-window window))))
433 ;; Create a list for this buffer only when needed.
434 (while (eq result t)
435 (setq index-alist (imenu--make-index-alist))
436 (setq result
437 (if (and mouse-triggered
438 (not imenu-always-use-completion-buffer-p))
439 (imenu--mouse-menu index-alist last-command-event)
440 (imenu--completion-buffer index-alist prompt)))
441 (and (eq result t)
442 (setq imenu--index-alist nil)))
443 result))
444
445(defun goto-index-pos ()
446 "Jump to selected part of buffer, using a buffer menu or mouse menu.
447
448See 'imenu-choose-buffer-index' for more information."
449 (interactive)
450 (let ((index-item (imenu-choose-buffer-index)))
451 (and index-item
452 (progn
453 (push-mark)
454 (goto-char (cdr index-item))))))
455
456;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
457;;;;
458;;;; Some examples of functions utilizing the framework of this
459;;;; package.
460;;;;
461;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
462
463;; Return the current/previous sexp and the location of the sexp (it's
464;; beginning) without moving the point.
465(defun imenu-example--name-and-position ()
466 (save-excursion
467 (forward-sexp -1)
468 (let ((beg (point))
469 (end (progn (forward-sexp) (point))))
470 (cons (buffer-substring beg end)
471 beg))))
472
473;;;
474;;; Lisp
475;;;
476
477(defun imenu-example--lisp-extract-index-name ()
478 ;; Example of a candidate for 'imenu-extract-index-name-function'.
479 ;; This will generate a flat index of definitions in a lisp file.
480 (save-match-data
481 (and (looking-at "(def")
482 (condition-case nil
483 (progn
484 (down-list 1)
485 (forward-sexp 2)
486 (let ((beg (point))
487 (end (progn (forward-sexp -1) (point))))
488 (buffer-substring beg end)))
489 (error nil)))))
490
491(defun imenu-example--create-lisp-index ()
492 ;; Example of a candidate for 'imenu-create-index-function'.
493 ;; It will generate a nested index of definitions.
494 (let ((index-alist '())
495 (index-var-alist '())
496 (index-unknown-alist '()))
497 (goto-char (point-max))
498 (imenu-progress-message 0)
499 ;; Search for the function
500 (while (beginning-of-defun)
501 (imenu-progress-message nil t)
502 (save-match-data
503 (and (looking-at "(def")
504 (save-excursion
505 (down-list 1)
506 (cond
507 ((looking-at "def\\(var\\|const\\)")
508 (forward-sexp 2)
509 (push (imenu-example--name-and-position)
510 index-var-alist))
511 ((looking-at "def\\(un\\|subst\\|macro\\|advice\\)")
512 (forward-sexp 2)
513 (push (imenu-example--name-and-position)
514 index-alist))
515 (t
516 (forward-sexp 2)
517 (push (imenu-example--name-and-position)
518 index-unknown-alist)))))))
519 (imenu-progress-message 100)
520 (and index-var-alist
521 (push (cons (imenu-create-submenu-name "Variables") index-var-alist)
522 index-alist))
523 (and index-unknown-alist
524 (push (cons (imenu-create-submenu-name "Syntax-unknown") index-unknown-alist)
525 index-alist))
526 index-alist))
527
528
529;;;
530;;; C
531;;;
532;; Regular expression to find C functions
533(defvar imenu-example--function-name-regexp-c
534 (concat
535 "^[a-zA-Z0-9]+[ \t]?" ; type specs; there can be no
536 "\\([a-zA-Z0-9_*]+[ \t]+\\)?" ; more than 3 tokens, right?
537 "\\([a-zA-Z0-9_*]+[ \t]+\\)?"
538 "\\([*&]+[ \t]*\\)?" ; pointer
539 "\\([a-zA-Z0-9_*]+\\)[ \t]*(" ; name
540 ))
541
542(defun imenu-example--create-c-index (&opter
543 "\\([a-zA-Z0-9_*]+\\)[ \t]*(" ; name
544 ))
545
546(defun imenu-example--create-c-index (&optional regexp)
547 (let ((index-alist '())
548 (char))
549 (goto-char (point-min))
550 (imenu-progress-message 0)
551 ;; Search for the function
552 (save-match-data
553 (while (re-search-forward
554 (or regexp imenu-example--function-name-regexp-c)
555 nil t)
556 (imenu-progress-message)
557 (backward-up-list 1)
558 (save-excursion
559 (goto-char (scan-sexps (point) 1))
560 (setq char (following-char)))
561 ;; Skip this function name if it is a prototype declaration.
562 (if (not (eq char ?\;))
563 (push (imenu-example--name-and-position) index-alist))))
564 (imenu-progress-message 100)
565 (nreverse index-alist)))
566
567;;;
568;;; C++
569;;;
570;; Regular expression to find C++ functions
571(defvar imenu-example--function-name-regexp-c++
572 (concat
573 "^[a-zA-Z0-9:]+[ \t]?" ; type specs; there can be no
574 "\\([a-zA-Z0-9_:~*]+[ \t]+\\)?" ; more than 3 tokens, right?
575 "\\([a-zA-Z0-9_:~*]+[ \t]+\\)?"
576 "\\([*&]+[ \t]*\\)?" ; pointer
577 "\\([a-zA-Z0-9_:*]+\\)[ \t]*(" ; name
578 ))
579(defun imenu-example--create-c++-index ()
580 (imenu-example--create-c-index imenu-example--function-name-regexp-c++))
581
582
583;;;
584;;; Example of hooks for the examples above
585;;; Put this in your .emacs.
586;;;
587;; (add-hook 'emacs-lisp-mode-hook
588;; (function
589;; (lambda ()
590;; (setq imenu-create-index-function
591;; (function imenu-example--create-lisp-index)))))
592
593;; (add-hook 'lisp-mode-hook
594;; (function
595;; (lambda ()
596;; (setq imenu-create-index-function
597;; (function imenu-example--create-lisp-index)))))
598
599;; (add-hook 'c++-mode-hook
600;; (function
601;; (lambda ()
602;; (setq imenu-create-index-function
603;; (function imenu-example--create-c++-index)))))
604
605;; (add-hook 'c-mode-hook
606;; (function
607;; (lambda ()
608;; (setq imenu-create-index-function
609;; (function imenu-example--create-c-index)))))
610
611(provide 'imenu)
612
613;;; imenu.el ends here