Commit | Line | Data |
---|---|---|
0a688fd0 RS |
1 | ;;; imenu.el --- Framework for mode-specific buffer indexes. |
2 | ||
f5632218 | 3 | ;; Copyright (C) 1994, 1995, 1996 Free Software Foundation, Inc. |
0a688fd0 RS |
4 | |
5 | ;; Author: Ake Stenhoff <etxaksf@aom.ericsson.se> | |
6 | ;; Lars Lindberg <lli@sypro.cap.se> | |
7 | ;; Created: 8 Feb 1994 | |
0a688fd0 | 8 | ;; Keywords: tools |
b578f267 EN |
9 | |
10 | ;; This file is part of GNU Emacs. | |
11 | ||
12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | |
0a688fd0 RS |
13 | ;; it under the terms of the GNU General Public License as published by |
14 | ;; the Free Software Foundation; either version 2, or (at your option) | |
15 | ;; any later version. | |
b578f267 EN |
16 | |
17 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
0a688fd0 RS |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
b578f267 | 21 | |
0a688fd0 | 22 | ;; You should have received a copy of the GNU General Public License |
b578f267 EN |
23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the |
24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
25 | ;; Boston, MA 02111-1307, USA. | |
0a688fd0 RS |
26 | |
27 | ;;; Commentary: | |
b578f267 | 28 | |
0a688fd0 RS |
29 | ;; Purpose of this package: |
30 | ;; To present a framework for mode-specific buffer indexes. | |
31 | ;; A buffer index is an alist of names and buffer positions. | |
32 | ;; For instance all functions in a C-file and their positions. | |
33 | ;; | |
34 | ;; How it works: | |
35 | ||
36 | ;; A mode-specific function is called to generate the index. It is | |
37 | ;; then presented to the user, who can choose from this index. | |
38 | ;; | |
39 | ;; The package comes with a set of example functions for how to | |
40 | ;; utilize this package. | |
41 | ||
2d24227e RS |
42 | ;; There are *examples* for index gathering functions/regular |
43 | ;; expressions for C/C++ and Lisp/Emacs Lisp but it is easy to | |
44 | ;; customize for other modes. A function for jumping to the chosen | |
45 | ;; index position is also supplied. | |
0a688fd0 | 46 | |
26d6bb60 RS |
47 | ;;; Thanks goes to |
48 | ;; [simon] - Simon Leinen simon@lia.di.epfl.ch | |
49 | ;; [dean] - Dean Andrews ada@unison.com | |
5d3b0f18 | 50 | ;; [alon] - Alon Albert al@mercury.co.il |
7804cd27 | 51 | ;; [greg] - Greg Thompson gregt@porsche.visix.COM |
615b306c | 52 | ;; [wolfgang] - Wolfgang Bangerth zcg51122@rpool1.rus.uni-stuttgart.de |
056ab244 | 53 | ;; [kai] - Kai Grossjohann grossjoh@linus.informatik.uni-dortmund.de |
af447694 | 54 | ;; [david] - David M. Smith dsmith@stats.adelaide.edu.au |
2d24227e RS |
55 | ;; [christian] - Christian Egli Christian.Egli@hcsd.hac.com |
56 | ;; [karl] - Karl Fogel kfogel@floss.life.uiuc.edu | |
57 | ||
0a688fd0 | 58 | ;;; Code |
b578f267 | 59 | |
0ee4f8ad | 60 | (eval-when-compile (require 'cl)) |
0a688fd0 RS |
61 | |
62 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
63 | ;;; | |
64 | ;;; Customizable variables | |
65 | ;;; | |
66 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
2d24227e | 67 | (defvar imenu-use-keymap-menu nil |
6c1bf12b | 68 | "*Non-nil means use a keymap when making the mouse menu.") |
2d24227e RS |
69 | |
70 | (defvar imenu-auto-rescan nil | |
6c1bf12b | 71 | "*Non-nil means Imenu should always rescan the buffers.") |
2d24227e RS |
72 | |
73 | (defvar imenu-auto-rescan-maxout 60000 | |
74 | "* auto-rescan is disabled in buffers larger than this. | |
6c1bf12b | 75 | This variable is buffer-local.") |
0a688fd0 RS |
76 | |
77 | (defvar imenu-always-use-completion-buffer-p nil | |
78 | "*Set this to non-nil for displaying the index in a completion buffer. | |
79 | ||
80 | Non-nil means always display the index in a completion buffer. | |
81 | Nil means display the index as a mouse menu when the mouse was | |
af447694 RS |
82 | used to invoke `imenu'. |
83 | `never' means never automatically display a listing of any kind.") | |
0a688fd0 RS |
84 | |
85 | (defvar imenu-sort-function nil | |
86 | "*The function to use for sorting the index mouse-menu. | |
87 | ||
88 | Affects only the mouse index menu. | |
89 | ||
90 | Set this to nil if you don't want any sorting (faster). | |
91 | The items in the menu are then presented in the order they were found | |
92 | in the buffer. | |
93 | ||
0ee4f8ad | 94 | Set it to `imenu--sort-by-name' if you want alphabetic sorting. |
0a688fd0 RS |
95 | |
96 | The function should take two arguments and return T if the first | |
97 | element should come before the second. The arguments are cons cells; | |
a4e104bf | 98 | \(NAME . POSITION). Look at `imenu--sort-by-name' for an example.") |
0a688fd0 RS |
99 | |
100 | (defvar imenu-max-items 25 | |
0c20ee61 | 101 | "*Maximum number of elements in an mouse menu for Imenu.") |
0a688fd0 | 102 | |
6c1bf12b | 103 | (defvar imenu-scanning-message "Scanning buffer for index (%3d%%)" |
0a688fd0 | 104 | "*Progress message during the index scanning of the buffer. |
7dea4e70 | 105 | If non-nil, user gets a message during the scanning of the buffer |
0a688fd0 RS |
106 | |
107 | Relevant only if the mode-specific function that creates the buffer | |
0ee4f8ad | 108 | index use `imenu-progress-message'.") |
0a688fd0 RS |
109 | |
110 | (defvar imenu-space-replacement "^" | |
111 | "*The replacement string for spaces in index names. | |
112 | Used when presenting the index in a completion-buffer to make the | |
113 | names work as tokens.") | |
114 | ||
115 | (defvar imenu-level-separator ":" | |
116 | "*The separator between index names of different levels. | |
117 | Used for making mouse-menu titles and for flattening nested indexes | |
118 | with name concatenation.") | |
119 | ||
2d24227e | 120 | ;;;###autoload |
615b306c | 121 | (defvar imenu-generic-expression nil |
2d24227e RS |
122 | "The regex pattern to use for creating a buffer index. |
123 | ||
124 | If non-nil this pattern is passed to `imenu-create-index-with-pattern' | |
125 | to create a buffer index. | |
126 | ||
127 | It is an alist with elements that look like this: (MENU-TITLE | |
128 | REGEXP INDEX). | |
129 | ||
130 | MENU-TITLE is a string used as the title for the submenu or nil if the | |
131 | entries are not nested. | |
132 | ||
133 | REGEXP is a regexp that should match a construct in the buffer that is | |
6c1bf12b RS |
134 | to be displayed in the menu; i.e., function or variable definitions, |
135 | etc. It contains a substring which is the name to appear in the | |
136 | menu. See the info section on Regexps for more information. | |
2d24227e RS |
137 | |
138 | INDEX points to the substring in REGEXP that contains the name (of the | |
139 | function, variable or type) that is to appear in the menu. | |
615b306c | 140 | |
2d24227e RS |
141 | For emacs-lisp-mode for example PATTERN would look like: |
142 | ||
c30c8a0c RS |
143 | '((nil \"^\\\\s-*(def\\\\(un\\\\|subst\\\\|macro\\\\|advice\\\\)\\\\s-+\\\\([-A-Za-z0-9+]+\\\\)\" 2) |
144 | (\"*Vars*\" \"^\\\\s-*(def\\\\(var\\\\|const\\\\)\\\\s-+\\\\([-A-Za-z0-9+]+\\\\)\" 2) | |
145 | (\"*Types*\" \"^\\\\s-*(def\\\\(type\\\\|struct\\\\|class\\\\|ine-condition\\\\)\\\\s-+\\\\([-A-Za-z0-9+]+\\\\)\" 2)) | |
2d24227e RS |
146 | |
147 | The variable is buffer-local.") | |
148 | ||
6c1bf12b | 149 | (make-variable-buffer-local 'imenu-generic-expression) |
615b306c | 150 | |
0a688fd0 RS |
151 | ;;;; Hooks |
152 | ||
153 | (defvar imenu-create-index-function 'imenu-default-create-index-function | |
154 | "The function to use for creating a buffer index. | |
155 | ||
156 | It should be a function that takes no arguments and returns an index | |
6c1bf12b RS |
157 | of the current buffer as an alist. The elements in the alist look |
158 | like: (INDEX-NAME . INDEX-POSITION). You may also nest index list like | |
a4e104bf | 159 | \(INDEX-NAME . INDEX-ALIST). |
0a688fd0 | 160 | |
0ee4f8ad | 161 | This function is called within a `save-excursion'. |
0a688fd0 RS |
162 | |
163 | The variable is buffer-local.") | |
164 | (make-variable-buffer-local 'imenu-create-index-function) | |
165 | ||
68e01f5a | 166 | (defvar imenu-prev-index-position-function 'beginning-of-defun |
0a688fd0 RS |
167 | "Function for finding the next index position. |
168 | ||
0ee4f8ad RS |
169 | If `imenu-create-index-function' is set to |
170 | `imenu-default-create-index-function', then you must set this variable | |
0a688fd0 RS |
171 | to a function that will find the next index, looking backwards in the |
172 | file. | |
173 | ||
174 | The function should leave point at the place to be connected to the | |
6c1bf12b | 175 | index and it should return nil when it doesn't find another index.") |
68e01f5a | 176 | (make-variable-buffer-local 'imenu-prev-index-position-function) |
0a688fd0 | 177 | |
68e01f5a | 178 | (defvar imenu-extract-index-name-function nil |
0a688fd0 RS |
179 | "Function for extracting the index name. |
180 | ||
0a688fd0 | 181 | This function is called after the function pointed out by |
68e01f5a RS |
182 | `imenu-prev-index-position-function'.") |
183 | (make-variable-buffer-local 'imenu-extract-index-name-function) | |
0a688fd0 | 184 | |
615b306c KH |
185 | ;;; |
186 | ;;; Macro to display a progress message. | |
187 | ;;; RELPOS is the relative position to display. | |
188 | ;;; If RELPOS is nil, then the relative position in the buffer | |
189 | ;;; is calculated. | |
190 | ;;; PREVPOS is the variable in which we store the last position displayed. | |
191 | (defmacro imenu-progress-message (prevpos &optional relpos reverse) | |
192 | (` (and | |
193 | imenu-scanning-message | |
194 | (let ((pos (, (if relpos | |
195 | relpos | |
196 | (` (imenu--relative-position (, reverse))))))) | |
197 | (if (, (if relpos t | |
198 | (` (> pos (+ 5 (, prevpos)))))) | |
199 | (progn | |
200 | (message imenu-scanning-message pos) | |
201 | (setq (, prevpos) pos))))))) | |
202 | ||
203 | ||
204 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
205 | ;;;; | |
206 | ;;;; Some examples of functions utilizing the framework of this | |
207 | ;;;; package. | |
208 | ;;;; | |
209 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
210 | ||
e064a4f9 | 211 | ;; Return the current/previous sexp and the location of the sexp (its |
615b306c KH |
212 | ;; beginning) without moving the point. |
213 | (defun imenu-example--name-and-position () | |
214 | (save-excursion | |
215 | (forward-sexp -1) | |
216 | (let ((beg (point)) | |
217 | (end (progn (forward-sexp) (point))) | |
218 | (marker (make-marker))) | |
219 | (set-marker marker beg) | |
220 | (cons (buffer-substring beg end) | |
221 | marker)))) | |
222 | ||
223 | ;;; | |
224 | ;;; Lisp | |
225 | ;;; | |
226 | ||
227 | (defun imenu-example--lisp-extract-index-name () | |
228 | ;; Example of a candidate for `imenu-extract-index-name-function'. | |
229 | ;; This will generate a flat index of definitions in a lisp file. | |
230 | (save-match-data | |
231 | (and (looking-at "(def") | |
232 | (condition-case nil | |
233 | (progn | |
234 | (down-list 1) | |
235 | (forward-sexp 2) | |
236 | (let ((beg (point)) | |
237 | (end (progn (forward-sexp -1) (point)))) | |
238 | (buffer-substring beg end))) | |
239 | (error nil))))) | |
240 | ||
241 | (defun imenu-example--create-lisp-index () | |
242 | ;; Example of a candidate for `imenu-create-index-function'. | |
243 | ;; It will generate a nested index of definitions. | |
244 | (let ((index-alist '()) | |
245 | (index-var-alist '()) | |
246 | (index-type-alist '()) | |
247 | (index-unknown-alist '()) | |
248 | prev-pos) | |
249 | (goto-char (point-max)) | |
250 | (imenu-progress-message prev-pos 0) | |
251 | ;; Search for the function | |
252 | (while (beginning-of-defun) | |
253 | (imenu-progress-message prev-pos nil t) | |
254 | (save-match-data | |
255 | (and (looking-at "(def") | |
256 | (save-excursion | |
257 | (down-list 1) | |
258 | (cond | |
259 | ((looking-at "def\\(var\\|const\\)") | |
260 | (forward-sexp 2) | |
261 | (push (imenu-example--name-and-position) | |
262 | index-var-alist)) | |
263 | ((looking-at "def\\(un\\|subst\\|macro\\|advice\\)") | |
264 | (forward-sexp 2) | |
265 | (push (imenu-example--name-and-position) | |
266 | index-alist)) | |
267 | ((looking-at "def\\(type\\|struct\\|class\\|ine-condition\\)") | |
268 | (forward-sexp 2) | |
269 | (if (= (char-after (1- (point))) ?\)) | |
270 | (progn | |
271 | (forward-sexp -1) | |
272 | (down-list 1) | |
273 | (forward-sexp 1))) | |
274 | (push (imenu-example--name-and-position) | |
275 | index-type-alist)) | |
276 | (t | |
277 | (forward-sexp 2) | |
278 | (push (imenu-example--name-and-position) | |
279 | index-unknown-alist))))))) | |
280 | (imenu-progress-message prev-pos 100) | |
281 | (and index-var-alist | |
0c20ee61 | 282 | (push (cons "Variables" index-var-alist) |
615b306c KH |
283 | index-alist)) |
284 | (and index-type-alist | |
0c20ee61 | 285 | (push (cons "Types" index-type-alist) |
615b306c KH |
286 | index-alist)) |
287 | (and index-unknown-alist | |
0c20ee61 | 288 | (push (cons "Syntax-unknown" index-unknown-alist) |
615b306c KH |
289 | index-alist)) |
290 | index-alist)) | |
291 | ||
615b306c KH |
292 | ;; Regular expression to find C functions |
293 | (defvar imenu-example--function-name-regexp-c | |
294 | (concat | |
295 | "^[a-zA-Z0-9]+[ \t]?" ; type specs; there can be no | |
296 | "\\([a-zA-Z0-9_*]+[ \t]+\\)?" ; more than 3 tokens, right? | |
297 | "\\([a-zA-Z0-9_*]+[ \t]+\\)?" | |
298 | "\\([*&]+[ \t]*\\)?" ; pointer | |
299 | "\\([a-zA-Z0-9_*]+\\)[ \t]*(" ; name | |
300 | )) | |
301 | ||
302 | (defun imenu-example--create-c-index (&optional regexp) | |
303 | (let ((index-alist '()) | |
304 | prev-pos char) | |
305 | (goto-char (point-min)) | |
306 | (imenu-progress-message prev-pos 0) | |
307 | ;; Search for the function | |
308 | (save-match-data | |
309 | (while (re-search-forward | |
310 | (or regexp imenu-example--function-name-regexp-c) | |
311 | nil t) | |
312 | (imenu-progress-message prev-pos) | |
313 | (backward-up-list 1) | |
314 | (save-excursion | |
315 | (goto-char (scan-sexps (point) 1)) | |
316 | (setq char (following-char))) | |
317 | ;; Skip this function name if it is a prototype declaration. | |
318 | (if (not (eq char ?\;)) | |
319 | (push (imenu-example--name-and-position) index-alist)))) | |
320 | (imenu-progress-message prev-pos 100) | |
321 | (nreverse index-alist))) | |
322 | ||
2d24227e | 323 | |
0a688fd0 RS |
324 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
325 | ;;; | |
326 | ;;; Internal variables | |
327 | ;;; | |
328 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
329 | ||
330 | ;; The item to use in the index for rescanning the buffer. | |
331 | (defconst imenu--rescan-item '("*Rescan*" . -99)) | |
332 | ||
333 | ;; The latest buffer index. | |
334 | ;; Buffer local. | |
335 | (defvar imenu--index-alist nil) | |
336 | (make-variable-buffer-local 'imenu--index-alist) | |
337 | ||
0a8e8bc6 KH |
338 | ;; The latest buffer index used to update the menu bar menu. |
339 | (defvar imenu--last-menubar-index-alist nil) | |
340 | (make-variable-buffer-local 'imenu--last-menubar-index-alist) | |
341 | ||
0a688fd0 | 342 | ;; History list for 'jump-to-function-in-buffer'. |
6c1bf12b | 343 | ;; Making this buffer local caused it not to work! |
0a688fd0 | 344 | (defvar imenu--history-list nil) |
0a688fd0 RS |
345 | |
346 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
347 | ;;; | |
348 | ;;; Internal support functions | |
349 | ;;; | |
350 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
351 | ||
352 | ;;; | |
353 | ;;; Sort function | |
354 | ;;; Sorts the items depending on their index name. | |
355 | ;;; An item look like (NAME . POSITION). | |
356 | ;;; | |
357 | (defun imenu--sort-by-name (item1 item2) | |
358 | (string-lessp (car item1) (car item2))) | |
359 | ||
360 | (defun imenu--relative-position (&optional reverse) | |
361 | ;; Support function to calculate relative position in buffer | |
362 | ;; Beginning of buffer is 0 and end of buffer is 100 | |
363 | ;; If REVERSE is non-nil then the beginning is 100 and the end is 0. | |
364 | (let ((pos (point)) | |
365 | (total (buffer-size))) | |
366 | (and reverse (setq pos (- total pos))) | |
367 | (if (> total 50000) | |
368 | ;; Avoid overflow from multiplying by 100! | |
369 | (/ (1- pos) (max (/ total 100) 1)) | |
370 | (/ (* 100 (1- pos)) (max total 1))))) | |
371 | ||
0a688fd0 RS |
372 | ;; Split LIST into sublists of max length N. |
373 | ;; Example (imenu--split '(1 2 3 4 5 6 7 8) 3)-> '((1 2 3) (4 5 6) (7 8)) | |
374 | (defun imenu--split (list n) | |
375 | (let ((remain list) | |
376 | (result '()) | |
377 | (sublist '()) | |
378 | (i 0)) | |
379 | (while remain | |
380 | (push (pop remain) sublist) | |
381 | (incf i) | |
382 | (and (= i n) | |
383 | ;; We have finished a sublist | |
384 | (progn (push (nreverse sublist) result) | |
385 | (setq i 0) | |
386 | (setq sublist '())))) | |
387 | ;; There might be a sublist (if the length of LIST mod n is != 0) | |
388 | ;; that has to be added to the result list. | |
389 | (and sublist | |
390 | (push (nreverse sublist) result)) | |
391 | (nreverse result))) | |
392 | ||
0c20ee61 RS |
393 | ;;; Split the alist MENULIST into a nested alist, if it is long enough. |
394 | ;;; In any case, add TITLE to the front of the alist. | |
0a688fd0 | 395 | (defun imenu--split-menu (menulist title) |
0c20ee61 RS |
396 | (if (> (length menulist) imenu-max-items) |
397 | (let ((count 0)) | |
398 | (cons title | |
399 | (mapcar | |
400 | (function | |
401 | (lambda (menu) | |
402 | (cons (format "(%s-%d)" title (setq count (1+ count))) | |
403 | menu))) | |
404 | (imenu--split menulist imenu-max-items)))) | |
405 | (cons title menulist))) | |
406 | ||
407 | ;;; Split up each long alist that are nested within ALIST | |
408 | ;;; into nested alists. | |
409 | (defun imenu--split-submenus (alist) | |
410 | (mapcar (function (lambda (elt) | |
411 | (if (and (consp elt) | |
412 | (stringp (car elt)) | |
413 | (listp (cdr elt))) | |
414 | (imenu--split-menu (cdr elt) (car elt)) | |
415 | elt))) | |
416 | alist)) | |
0a688fd0 RS |
417 | |
418 | ;;; | |
419 | ;;; Find all items in this buffer that should be in the index. | |
420 | ;;; Returns an alist on the form | |
421 | ;;; ((NAME . POSITION) (NAME . POSITION) ...) | |
422 | ;;; | |
423 | ||
0a8e8bc6 | 424 | (defun imenu--make-index-alist (&optional noerror) |
0a688fd0 | 425 | ;; Create a list for this buffer only when needed. |
2d24227e RS |
426 | (or (and imenu--index-alist |
427 | (or (not imenu-auto-rescan) | |
428 | (and imenu-auto-rescan | |
429 | (> (buffer-size) imenu-auto-rescan-maxout)))) | |
0a688fd0 RS |
430 | ;; Get the index |
431 | (setq imenu--index-alist | |
432 | (save-excursion | |
433 | (funcall imenu-create-index-function)))) | |
0a8e8bc6 | 434 | (or imenu--index-alist noerror |
6c1bf12b | 435 | (error "No items suitable for an index found in this buffer")) |
0a8e8bc6 KH |
436 | (or imenu--index-alist |
437 | (setq imenu--index-alist (list nil))) | |
0a688fd0 RS |
438 | ;; Add a rescan option to the index. |
439 | (cons imenu--rescan-item imenu--index-alist)) | |
5d3b0f18 RS |
440 | ;;; |
441 | ;;; Find all markers in alist and makes | |
442 | ;;; them point nowhere. | |
443 | ;;; | |
444 | (defun imenu--cleanup (&optional alist) | |
445 | ;; Sets the markers in imenu--index-alist | |
446 | ;; point nowhere. | |
447 | ;; if alist is provided use that list. | |
4818d210 RS |
448 | (or alist |
449 | (setq alist imenu--index-alist)) | |
450 | (and alist | |
7804cd27 | 451 | (mapcar |
5d3b0f18 RS |
452 | (function |
453 | (lambda (item) | |
454 | (cond | |
455 | ((markerp (cdr item)) | |
456 | (set-marker (cdr item) nil)) | |
4818d210 | 457 | ((consp (cdr item)) |
5d3b0f18 | 458 | (imenu--cleanup (cdr item)))))) |
4818d210 | 459 | alist) |
615b306c KH |
460 | t)) |
461 | ||
0a8e8bc6 | 462 | (defun imenu--create-keymap-2 (alist counter &optional commands) |
2d24227e RS |
463 | (let ((map nil)) |
464 | (mapcar | |
465 | (function | |
466 | (lambda (item) | |
467 | (cond | |
468 | ((listp (cdr item)) | |
fc0ac20d KH |
469 | (append (list (setq counter (1+ counter)) |
470 | (car item) 'keymap (car item)) | |
0a8e8bc6 | 471 | (imenu--create-keymap-2 (cdr item) (+ counter 10) commands))) |
2d24227e | 472 | (t |
fc0ac20d KH |
473 | (let ((end (if commands `(lambda () (interactive) |
474 | (imenu--menubar-select ',item)) | |
0a8e8bc6 | 475 | (cons '(nil) t)))) |
2d24227e RS |
476 | (cons (car item) |
477 | (cons (car item) end)))) | |
478 | ))) | |
479 | alist))) | |
480 | ||
0a8e8bc6 KH |
481 | ;; If COMMANDS is non-nil, make a real keymap |
482 | ;; with a real command used as the definition. | |
483 | ;; If it is nil, make something suitable for x-popup-menu. | |
484 | (defun imenu--create-keymap-1 (title alist &optional commands) | |
485 | (append (list 'keymap title) (imenu--create-keymap-2 alist 0 commands))) | |
2d24227e RS |
486 | |
487 | ||
488 | (defun imenu--in-alist (str alist) | |
489 | "Check whether the string STR is contained in multi-level ALIST." | |
490 | (let (elt head tail res) | |
491 | (setq res nil) | |
492 | (while alist | |
493 | (setq elt (car alist) | |
494 | tail (cdr elt) | |
495 | alist (cdr alist) | |
496 | head (car elt)) | |
497 | (if (string= str head) | |
498 | (setq alist nil res elt) | |
499 | (if (and (listp tail) | |
500 | (setq res (imenu--in-alist str tail))) | |
501 | (setq alist nil)))) | |
502 | res)) | |
503 | ||
0a688fd0 RS |
504 | (defun imenu-default-create-index-function () |
505 | "*Wrapper for index searching functions. | |
506 | ||
507 | Moves point to end of buffer and then repeatedly calls | |
68e01f5a | 508 | `imenu-prev-index-position-function' and `imenu-extract-index-name-function'. |
0a688fd0 | 509 | Their results are gathered into an index alist." |
3e062f78 RS |
510 | ;; These should really be done by setting imenu-create-index-function |
511 | ;; in these major modes. But save that change for later. | |
615b306c KH |
512 | (cond ((and (fboundp imenu-prev-index-position-function) |
513 | (fboundp imenu-extract-index-name-function)) | |
3e062f78 | 514 | (let ((index-alist '()) |
615b306c | 515 | prev-pos name) |
3e062f78 | 516 | (goto-char (point-max)) |
7dea4e70 | 517 | (imenu-progress-message prev-pos 0 t) |
3e062f78 RS |
518 | ;; Search for the function |
519 | (while (funcall imenu-prev-index-position-function) | |
7dea4e70 | 520 | (imenu-progress-message prev-pos nil t) |
3e062f78 RS |
521 | (save-excursion |
522 | (setq name (funcall imenu-extract-index-name-function))) | |
523 | (and (stringp name) | |
524 | (push (cons name (point)) index-alist))) | |
7dea4e70 | 525 | (imenu-progress-message prev-pos 100 t) |
615b306c KH |
526 | index-alist)) |
527 | ;; Use generic expression if possible. | |
528 | ((and imenu-generic-expression) | |
e064a4f9 | 529 | (imenu--generic-function imenu-generic-expression)) |
615b306c KH |
530 | (t |
531 | (error "The mode \"%s\" does not take full advantage of imenu.el yet." | |
532 | mode-name)))) | |
0a688fd0 RS |
533 | |
534 | (defun imenu--replace-spaces (name replacement) | |
535 | ;; Replace all spaces in NAME with REPLACEMENT. | |
536 | ;; That second argument should be a string. | |
537 | (mapconcat | |
538 | (function | |
539 | (lambda (ch) | |
540 | (if (char-equal ch ?\ ) | |
541 | replacement | |
542 | (char-to-string ch)))) | |
543 | name | |
544 | "")) | |
545 | ||
546 | (defun imenu--flatten-index-alist (index-alist &optional concat-names prefix) | |
547 | ;; Takes a nested INDEX-ALIST and returns a flat index alist. | |
548 | ;; If optional CONCAT-NAMES is non-nil, then a nested index has its | |
549 | ;; name and a space concatenated to the names of the children. | |
550 | ;; Third argument PREFIX is for internal use only. | |
551 | (mapcan | |
552 | (function | |
553 | (lambda (item) | |
554 | (let* ((name (car item)) | |
555 | (pos (cdr item)) | |
556 | (new-prefix (and concat-names | |
557 | (if prefix | |
558 | (concat prefix imenu-level-separator name) | |
559 | name)))) | |
560 | (cond | |
5d3b0f18 | 561 | ((or (markerp pos) (numberp pos)) |
0a688fd0 RS |
562 | (list (cons new-prefix pos))) |
563 | (t | |
564 | (imenu--flatten-index-alist pos new-prefix)))))) | |
565 | index-alist)) | |
566 | ||
615b306c KH |
567 | ;;; |
568 | ;;; Generic index gathering function. | |
569 | ;;; | |
2d24227e RS |
570 | |
571 | (defun imenu--generic-function (patterns) | |
572 | ;; Built on some ideas that Erik Naggum <erik@naggum.no> once posted | |
573 | ;; to comp.emacs | |
574 | "Return an index of the current buffer as an alist. | |
575 | ||
576 | PATTERN is an alist with elements that look like this: (MENU-TITLE | |
6c1bf12b | 577 | REGEXP INDEX). |
2d24227e RS |
578 | |
579 | MENU-TITLE is a string used as the title for the submenu or nil if the | |
580 | entries are not nested. | |
581 | ||
582 | REGEXP is a regexp that should match a construct in the buffer that is | |
6c1bf12b RS |
583 | to be displayed in the menu; i.e., function or variable definitions, |
584 | etc. It contains a substring which is the name to appear in the | |
585 | menu. See the info section on Regexps for more information. | |
2d24227e RS |
586 | |
587 | INDEX points to the substring in REGEXP that contains the name (of the | |
588 | function, variable or type) that is to appear in the menu. | |
589 | ||
590 | For emacs-lisp-mode for example PATTERN would look like: | |
591 | ||
c30c8a0c RS |
592 | '((nil \"^\\\\s-*(def\\\\(un\\\\|subst\\\\|macro\\\\|advice\\\\)\\\\s-+\\\\([-A-Za-z0-9]+\\\\)\" 2) |
593 | (\"*Vars*\" \"^\\\\s-*(def\\\\(var\\\\|const\\\\)\\\\s-+\\\\([-A-Za-z0-9]+\\\\)\" 2) | |
594 | (\"*Types*\" \"^\\\\s-*(def\\\\(type\\\\|struct\\\\|class\\\\|ine-condition\\\\)\\\\s-+\\\\([-A-Za-z0-9]+\\\\)\" 2))' | |
2d24227e | 595 | |
6c1bf12b RS |
596 | Returns an index of the current buffer as an alist. The elements in |
597 | the alist look like: (INDEX-NAME . INDEX-POSITION). They may also be | |
2d24227e RS |
598 | nested index lists like (INDEX-NAME . INDEX-ALIST) depending on |
599 | pattern. | |
600 | ||
601 | \(imenu--generic-function PATTERN\)." | |
602 | ||
603 | (let ((index-alist (list 'dummy)) | |
604 | (found nil) | |
605 | (global-regexp | |
606 | (concat "\\(" | |
607 | (mapconcat | |
608 | (function (lambda (pattern) (identity (cadr pattern)))) | |
609 | patterns "\\)\\|\\(") | |
610 | "\\)")) | |
611 | prev-pos) | |
612 | ||
615b306c KH |
613 | (goto-char (point-max)) |
614 | (imenu-progress-message prev-pos 0 t) | |
2d24227e RS |
615 | (save-match-data |
616 | (while (re-search-backward global-regexp nil t) | |
617 | (imenu-progress-message prev-pos nil t) | |
618 | (setq found nil) | |
619 | (save-excursion | |
620 | (goto-char (match-beginning 0)) | |
621 | (mapcar | |
622 | (function | |
623 | (lambda (pat) | |
624 | (let ((menu-title (car pat)) | |
625 | (regexp (cadr pat)) | |
626 | (index (caddr pat))) | |
627 | (if (and (not found) ; Only allow one entry; | |
628 | (looking-at regexp)) | |
629 | (let ((beg (match-beginning index)) | |
630 | (end (match-end index))) | |
631 | (setq found t) | |
632 | (push | |
e1de3014 | 633 | (cons (buffer-substring-no-properties beg end) beg) |
2d24227e | 634 | (cdr |
0c20ee61 | 635 | (or (assoc menu-title index-alist) |
2d24227e | 636 | (car (push |
0c20ee61 | 637 | (cons menu-title '()) |
2d24227e | 638 | index-alist)))))))))) |
0c20ee61 RS |
639 | patterns)))) |
640 | (imenu-progress-message prev-pos 100 t) | |
641 | (let ((main-element (assq nil index-alist))) | |
642 | (nconc (delq main-element (delq 'dummy index-alist)) main-element)))) | |
615b306c | 643 | |
0a688fd0 RS |
644 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
645 | ;;; | |
646 | ;;; The main functions for this package! | |
647 | ;;; | |
648 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
649 | ||
650 | (defun imenu--completion-buffer (index-alist &optional prompt) | |
651 | "Let the user select from INDEX-ALIST in a completion buffer with PROMPT. | |
652 | ||
653 | Returns t for rescan and otherwise a position number." | |
654 | ;; Create a list for this buffer only when needed. | |
655 | (let (name choice | |
615b306c KH |
656 | (prepared-index-alist |
657 | (mapcar | |
658 | (function | |
659 | (lambda (item) | |
660 | (cons (imenu--replace-spaces (car item) imenu-space-replacement) | |
661 | (cdr item)))) | |
662 | index-alist))) | |
af447694 RS |
663 | (if (eq imenu-always-use-completion-buffer-p 'never) |
664 | (setq name (completing-read (or prompt "Index item: ") | |
665 | prepared-index-alist | |
666 | nil t nil 'imenu--history-list)) | |
667 | (save-window-excursion | |
668 | ;; Display the completion buffer | |
669 | (with-output-to-temp-buffer "*Completions*" | |
670 | (display-completion-list | |
671 | (all-completions "" prepared-index-alist ))) | |
672 | (let ((minibuffer-setup-hook | |
673 | (function (lambda () | |
674 | (let ((buffer (current-buffer))) | |
675 | (save-excursion | |
676 | (set-buffer "*Completions*") | |
677 | (setq completion-reference-buffer buffer))))))) | |
678 | ;; Make a completion question | |
679 | (setq name (completing-read (or prompt "Index item: ") | |
680 | prepared-index-alist | |
681 | nil t nil 'imenu--history-list))))) | |
3e062f78 RS |
682 | (cond ((not (stringp name)) |
683 | nil) | |
684 | ((string= name (car imenu--rescan-item)) | |
685 | t) | |
686 | (t | |
687 | (setq choice (assoc name prepared-index-alist)) | |
688 | (if (listp (cdr choice)) | |
689 | (imenu--completion-buffer (cdr choice) prompt) | |
690 | choice))))) | |
68e01f5a | 691 | |
0a688fd0 RS |
692 | (defun imenu--mouse-menu (index-alist event &optional title) |
693 | "Let the user select from a buffer index from a mouse menu. | |
694 | ||
695 | INDEX-ALIST is the buffer index and EVENT is a mouse event. | |
696 | ||
697 | Returns t for rescan and otherwise a position number." | |
0c20ee61 | 698 | (setq index-alist (imenu--split-submenus index-alist)) |
0a688fd0 RS |
699 | (let* ((menu (imenu--split-menu |
700 | (if imenu-sort-function | |
0ee4f8ad RS |
701 | (sort |
702 | (let ((res nil) | |
703 | (oldlist index-alist)) | |
704 | ;; Copy list method from the cl package `copy-list' | |
705 | (while (consp oldlist) (push (pop oldlist) res)) | |
706 | (prog1 (nreverse res) (setcdr res oldlist))) | |
707 | imenu-sort-function) | |
0a688fd0 RS |
708 | index-alist) |
709 | (or title (buffer-name)))) | |
710 | position) | |
2d24227e RS |
711 | (and imenu-use-keymap-menu |
712 | (setq menu (imenu--create-keymap-1 (car menu) | |
713 | (if (< 1 (length (cdr menu))) | |
714 | (cdr menu) | |
715 | (cdr (cadr menu)))))) | |
cd7ac8e8 RS |
716 | |
717 | (or imenu-use-keymap-menu | |
718 | (setq menu (list "Imenu" (delq nil menu)))) | |
0a688fd0 | 719 | (setq position (x-popup-menu event menu)) |
2d24227e RS |
720 | (if imenu-use-keymap-menu |
721 | (progn | |
722 | (cond | |
723 | ((and (listp position) | |
724 | (numberp (car position)) | |
725 | (stringp (nth (1- (length position)) position))) | |
726 | (setq position (nth (1- (length position)) position))) | |
727 | ((and (stringp (car position)) | |
728 | (null (cdr position))) | |
729 | (setq position (car position)))))) | |
0a688fd0 RS |
730 | (cond |
731 | ((eq position nil) | |
732 | position) | |
26d6bb60 | 733 | ((listp position) |
0a688fd0 RS |
734 | (imenu--mouse-menu position event |
735 | (if title | |
736 | (concat title imenu-level-separator | |
737 | (car (rassq position index-alist))) | |
738 | (car (rassq position index-alist))))) | |
2d24227e RS |
739 | ((stringp position) |
740 | (or (string= position (car imenu--rescan-item)) | |
741 | (imenu--in-alist position index-alist))) | |
742 | ((or (= position (cdr imenu--rescan-item)) | |
743 | (and (stringp position) | |
744 | (string= position (car imenu--rescan-item)))) | |
0a688fd0 RS |
745 | t) |
746 | (t | |
747 | (rassq position index-alist))))) | |
748 | ||
26d6bb60 | 749 | (defun imenu-choose-buffer-index (&optional prompt alist) |
0a688fd0 RS |
750 | "Let the user select from a buffer index and return the chosen index. |
751 | ||
752 | If the user originally activated this function with the mouse, a mouse | |
0a688fd0 RS |
753 | menu is used. Otherwise a completion buffer is used and the user is |
754 | prompted with PROMPT. | |
755 | ||
26d6bb60 RS |
756 | If you call this function with index alist ALIST, then it lets the user |
757 | select from ALIST. | |
758 | ||
0ee4f8ad | 759 | With no index alist ALIST, it calls `imenu--make-index-alist' to |
26d6bb60 RS |
760 | create the index alist. |
761 | ||
0ee4f8ad | 762 | If `imenu-always-use-completion-buffer-p' is non-nil, then the |
0a688fd0 RS |
763 | completion buffer is always used, no matter if the mouse was used or |
764 | not. | |
765 | ||
766 | The returned value is on the form (INDEX-NAME . INDEX-POSITION)." | |
767 | (let (index-alist | |
7dea4e70 | 768 | (mouse-triggered (listp last-nonmenu-event)) |
0a688fd0 RS |
769 | (result t) ) |
770 | ;; If selected by mouse, see to that the window where the mouse is | |
771 | ;; really is selected. | |
772 | (and mouse-triggered | |
4cde72b4 | 773 | (not (equal last-nonmenu-event '(menu-bar))) |
7dea4e70 | 774 | (let ((window (posn-window (event-start last-nonmenu-event)))) |
4a840d8b | 775 | (or (framep window) (null window) (select-window window)))) |
0a688fd0 RS |
776 | ;; Create a list for this buffer only when needed. |
777 | (while (eq result t) | |
26d6bb60 | 778 | (setq index-alist (if alist alist (imenu--make-index-alist))) |
0a688fd0 RS |
779 | (setq result |
780 | (if (and mouse-triggered | |
781 | (not imenu-always-use-completion-buffer-p)) | |
7dea4e70 | 782 | (imenu--mouse-menu index-alist last-nonmenu-event) |
0a688fd0 RS |
783 | (imenu--completion-buffer index-alist prompt))) |
784 | (and (eq result t) | |
5d3b0f18 | 785 | (imenu--cleanup) |
0a688fd0 RS |
786 | (setq imenu--index-alist nil))) |
787 | result)) | |
788 | ||
2d24227e | 789 | ;;;###autoload |
5d3b0f18 | 790 | (defun imenu-add-to-menubar (name) |
d1757026 | 791 | "Adds an `imenu' entry to the menu bar for the current buffer. |
0a8e8bc6 | 792 | NAME is a string used to name the menu bar item. |
d1757026 | 793 | See the command `imenu' for more information." |
0a8e8bc6 | 794 | (interactive "sImenu menu item name: ") |
d1757026 RS |
795 | (let ((newmap (make-sparse-keymap)) |
796 | (menu-bar (lookup-key (current-local-map) [menu-bar]))) | |
797 | (define-key newmap [menu-bar] | |
798 | (append (make-sparse-keymap) menu-bar)) | |
799 | (define-key newmap [menu-bar index] | |
800 | (cons name (nconc (make-sparse-keymap "Imenu") | |
801 | (make-sparse-keymap)))) | |
802 | (use-local-map (append newmap (current-local-map)))) | |
0a8e8bc6 KH |
803 | (add-hook 'menu-bar-update-hook 'imenu-update-menubar)) |
804 | ||
6d7a4832 KH |
805 | (defvar imenu-buffer-menubar nil) |
806 | ||
0a8e8bc6 KH |
807 | (defun imenu-update-menubar () |
808 | (and (current-local-map) | |
809 | (keymapp (lookup-key (current-local-map) [menu-bar index])) | |
810 | (let ((index-alist (imenu--make-index-alist t))) | |
811 | ;; Don't bother updating if the index-alist has not changed | |
812 | ;; since the last time we did it. | |
813 | (or (equal index-alist imenu--last-menubar-index-alist) | |
814 | (let (menu menu1 old) | |
815 | (setq imenu--last-menubar-index-alist index-alist) | |
0c20ee61 | 816 | (setq index-alist (imenu--split-submenus index-alist)) |
0a8e8bc6 KH |
817 | (setq menu (imenu--split-menu |
818 | (if imenu-sort-function | |
819 | (sort | |
820 | (let ((res nil) | |
821 | (oldlist index-alist)) | |
822 | ;; Copy list method from the cl package `copy-list' | |
823 | (while (consp oldlist) (push (pop oldlist) res)) | |
824 | (prog1 (nreverse res) (setcdr res oldlist))) | |
825 | imenu-sort-function) | |
826 | index-alist) | |
827 | (buffer-name))) | |
828 | (setq menu1 (imenu--create-keymap-1 (car menu) | |
829 | (if (< 1 (length (cdr menu))) | |
830 | (cdr menu) | |
831 | (cdr (car (cdr menu)))) | |
832 | t)) | |
833 | (setq old (lookup-key (current-local-map) [menu-bar index])) | |
834 | (if (keymapp old) | |
835 | (setcdr (nthcdr 2 old) menu1))))))) | |
836 | ||
837 | (defun imenu--menubar-select (item) | |
838 | "Use Imenu to select the function or variable named in this menu item." | |
e63679b8 RS |
839 | (if (equal item '("*Rescan*" . -99)) |
840 | (progn | |
841 | (imenu--cleanup) | |
842 | (setq imenu--index-alist nil) | |
843 | (imenu-update-menubar)) | |
844 | (imenu item))) | |
5d3b0f18 | 845 | |
68e01f5a | 846 | ;;;###autoload |
6c1bf12b | 847 | (defun imenu (index-item) |
68e01f5a | 848 | "Jump to a place in the buffer chosen using a buffer menu or mouse menu. |
0ee4f8ad | 849 | See `imenu-choose-buffer-index' for more information." |
6c1bf12b RS |
850 | (interactive |
851 | (list (save-restriction | |
852 | (widen) | |
0a8e8bc6 KH |
853 | (car (imenu-choose-buffer-index))))) |
854 | ;; Convert a string to an alist element. | |
855 | (if (stringp index-item) | |
856 | (setq index-item (assoc index-item (imenu--make-index-alist)))) | |
6c1bf12b RS |
857 | (and index-item |
858 | (progn | |
859 | (push-mark) | |
860 | (cond | |
861 | ((markerp (cdr index-item)) | |
862 | (if (or ( > (marker-position (cdr index-item)) (point-min)) | |
863 | ( < (marker-position (cdr index-item)) (point-max))) | |
864 | ;; widen if outside narrowing | |
865 | (widen)) | |
866 | (goto-char (marker-position (cdr index-item)))) | |
867 | (t | |
868 | (if (or ( > (cdr index-item) (point-min)) | |
869 | ( < (cdr index-item) (point-max))) | |
870 | ;; widen if outside narrowing | |
871 | (widen)) | |
872 | (goto-char (cdr index-item))))))) | |
5d3b0f18 | 873 | |
0a688fd0 RS |
874 | (provide 'imenu) |
875 | ||
876 | ;;; imenu.el ends here |