Guido van Rossum | 3ec97e5 | 1995-10-09 21:27:37 +0000 | [diff] [blame] | 1 | ;;; PYIMENU.EL --- |
| 2 | |
| 3 | ;; Copyright (C) 1995 Perry A. Stoll |
| 4 | |
| 5 | ;; Author: Perry A. Stoll <stoll@atr-sw.atr.co.jp> |
| 6 | ;; Created: 12 May 1995 |
| 7 | ;; Version: 1.0 |
| 8 | ;; Keywords: tools python imenu |
| 9 | |
| 10 | ;; This program is free software; you can redistribute it and/or modify |
| 11 | ;; it under the terms of the GNU General Public License as published by |
| 12 | ;; the Free Software Foundation; either version 2, or (at your option) |
| 13 | ;; any later version. |
| 14 | |
| 15 | ;; This program is distributed in the hope that it will be useful, |
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | ;; GNU General Public License for more details. |
| 19 | |
| 20 | ;; A copy of the GNU General Public License can be obtained from the |
| 21 | ;; Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, |
| 22 | ;; USA. |
| 23 | |
| 24 | ;;;; COMMENTS |
| 25 | |
| 26 | ;; I use the imenu package a lot for looking at Lisp and C/C++ |
| 27 | ;; code. When I started using Python, I was dismayed that I couldn't |
| 28 | ;; use it to look at Python source. So here's a rough go at it. |
| 29 | |
| 30 | ;;;; USAGE |
| 31 | |
| 32 | ;; This program is used in conjunction with the imenu package. When |
| 33 | ;; you call imenu in python-mode in a python buffer, a list of |
| 34 | ;; functions and classes is built. The top level menu has a list of |
| 35 | ;; all functions and classes defined at the top indentation |
| 36 | ;; level. Classes which have methods defined in them have a submenu |
| 37 | ;; which contains all definitions contained in them. Selecting any |
| 38 | ;; item will bring you to that point in the file. |
| 39 | |
| 40 | ;;;; INSTALLATION |
| 41 | |
| 42 | ;; You know the routine: |
| 43 | ;; 1) Save this file as pyimenu.el, |
| 44 | ;; 2) Place that file somewhere in your emacs load path (maybe ~/emacs |
| 45 | ;; or ~/emacs/lisp), |
| 46 | ;; 3) Byte compile it (M-x byte-compile-file), |
| 47 | ;; 4) Add the following (between "cut here" and "end here") to your |
| 48 | ;; ~/.emacs file, |
| 49 | ;; 5) Reboot. (joke: DON'T do that, although you'll probably have to |
| 50 | ;; either reload your ~/.emacs file or start a new emacs) |
| 51 | |
| 52 | ;;--------------cut here------------------------------------------- |
| 53 | ;;;; Load the pyimenu index function |
| 54 | ;;(autoload 'imenu "imenu" nil t) |
| 55 | ;;(autoload 'imenu-example--create-python-index "pyimenu") |
| 56 | ;;;; Add the index creation function to the python-mode-hook |
| 57 | ;;(add-hook 'python-mode-hook |
| 58 | ;; (function |
| 59 | ;; (lambda () |
| 60 | ;; (setq imenu-create-index-function |
| 61 | ;; (function imenu-example--create-python-index))))) |
| 62 | ;;----------------end here-------------------------------------------- |
| 63 | ;; |
| 64 | ;; That is all you need. Of course, the following provides a more |
| 65 | ;; useful interface. i.e. this is how I have it set up ;-) |
| 66 | ;; |
| 67 | ;;----------------optionally cut here---------------------------------- |
| 68 | ;;(autoload 'imenu-add-to-menubar "imenu" nil t) |
| 69 | ;;(defun my-imenu-install-hook () |
| 70 | ;; (imenu-add-to-menubar (format "%s-%s" "IM" mode-name))) |
| 71 | ;;(add-hook 'python-mode-hook (function my-imenu-install-hook)) |
| 72 | ;;;; Bind imenu to some convenient (?) mouse key. This really lets you |
| 73 | ;;;; fly around the buffer. Here it is set to Meta-Shift-Mouse3Click. |
| 74 | ;;(global-set-key [M-S-down-mouse-3] (function imenu)) |
| 75 | ;;-----------------optionaly end here----------------------------------- |
| 76 | |
| 77 | ;;;; CAVEATS/NOTES |
| 78 | |
| 79 | ;; 0) I'm not a professional elisp programmer and it shows in the code |
| 80 | ;; below. If anyone there has suggestions/changes, I'd love to |
| 81 | ;; hear them. I've tried the code out on a bunch of python files |
| 82 | ;; from the python-1.1.1 Demo distribution and it worked with |
| 83 | ;; them - your mileage may very. |
| 84 | ;; |
| 85 | ;; 1) You must have the imenu package to use this file. This file |
| 86 | ;; works with imenu version 1.11 (the version included with emacs |
| 87 | ;; 19.28) and imenu version 1.14; if you have a later version, this |
| 88 | ;; may not work with it. |
| 89 | ;; |
| 90 | ;; 2) This setup assumes you also have python-mode.el, so that it can |
| 91 | ;; use the python-mode-hook. It comes with the python distribution. |
| 92 | ;; |
| 93 | ;; 3) I don't have the Python 1.2 distribution yet, so again, this may |
| 94 | ;; not work with that. |
| 95 | ;; |
| 96 | |
| 97 | (require 'imenu) |
| 98 | |
| 99 | ;;; |
| 100 | ;;; VARIABLES: customizable in your .emacs file. |
| 101 | ;;; |
| 102 | |
| 103 | (defvar imenu-example--python-show-method-args-p nil |
| 104 | "*When using imenu package with python-mode, whether the arguments of |
| 105 | the function/methods should be printed in the imenu buffer in addition |
| 106 | to the function/method name. If non-nil, args are printed.") |
| 107 | |
| 108 | ;;; |
| 109 | ;;; VARIABLES: internal use. |
| 110 | ;;; |
| 111 | (defvar imenu-example--python-class-regexp |
| 112 | (concat ; <<classes>> |
| 113 | "\\(" ; |
| 114 | "^[ \t]*" ; newline and maybe whitespace |
| 115 | "\\(class[ \t]+[a-zA-Z0-9_]+\\)" ; class name |
| 116 | ; possibly multiple superclasses |
| 117 | "\\([ \t]*\\((\\([a-zA-Z0-9_, \t\n]\\)*)\\)?\\)" |
| 118 | "[ \t]*:" ; and the final : |
| 119 | "\\)" ; >>classes<< |
| 120 | ) |
| 121 | "Regexp for Python classes for use with the imenu package." |
| 122 | ) |
| 123 | |
| 124 | (defvar imenu-example--python-method-regexp |
| 125 | (concat ; <<methods and functions>> |
| 126 | "\\(" ; |
| 127 | "^[ \t]*" ; new line and maybe whitespace |
| 128 | "\\(def[ \t]+" ; function definitions start with def |
| 129 | "\\([a-zA-Z0-9_]+\\)" ; name is here |
| 130 | ; function arguments... |
| 131 | "[ \t]*(\\([a-zA-Z0-9_=,\* \t\n]*\\))" |
| 132 | "\\)" ; end of def |
| 133 | "[ \t]*:" ; and then the : |
| 134 | "\\)" ; >>methods and functions<< |
| 135 | ) |
| 136 | "Regexp for Python methods/functions for use with the imenu package." |
| 137 | ) |
| 138 | |
| 139 | (defvar imenu-example--python-method-no-arg-parens '(2 8) |
| 140 | "Indicies into the parenthesis list of the regular expression for |
| 141 | python for use with imenu. Using these values will result in smaller |
| 142 | imenu lists, as arguments to functions are not listed. |
| 143 | |
| 144 | See the variable imenu-example--python-show-method-args-p to for |
| 145 | information") |
| 146 | |
| 147 | (defvar imenu-example--python-method-arg-parens '(2 7) |
| 148 | "Indicies into the parenthesis list of the regular expression for |
| 149 | python for use with imenu. Using these values will result in large |
| 150 | imenu lists, as arguments to functions are listed. |
| 151 | |
| 152 | See the variable imenu-example--python-show-method-args-p to for |
| 153 | information") |
| 154 | |
| 155 | ;; Note that in this format, this variable can still be used with the |
| 156 | ;; imenu--generic-function. Otherwise, there is no real reason to have |
| 157 | ;; it. |
| 158 | (defvar imenu-example--generic-python-expression |
| 159 | (cons |
| 160 | (concat |
| 161 | imenu-example--python-class-regexp |
| 162 | "\\|" ; or... |
| 163 | imenu-example--python-method-regexp |
| 164 | ) |
| 165 | imenu-example--python-method-no-arg-parens) |
| 166 | "Generic Python expression which may be used directly with imenu by |
| 167 | setting the variable imenu-generic-expression to this value. Also, see |
| 168 | the function \\[imenu-example--create-python-index] for an alternate |
| 169 | way of finding the index.") |
| 170 | |
| 171 | ;; These next two variables are used when searching for the python |
| 172 | ;; class/definitions. Just saving some time in accessing the |
| 173 | ;; generic-python-expression, really. |
| 174 | (defvar imenu-example--python-generic-regexp) |
| 175 | (defvar imenu-example--python-generic-parens) |
| 176 | |
| 177 | ;;; |
| 178 | ;;; CODE: |
| 179 | ;;; |
| 180 | |
| 181 | ;; Note: |
| 182 | ;; At first, I tried using some of the functions supplied by |
| 183 | ;; python-mode to navigate through functions and classes, but after a |
| 184 | ;; while, I decided dump it. This file is relatively self contained |
| 185 | ;; and I liked it that. |
| 186 | |
| 187 | ;;;###autoload |
| 188 | (defun imenu-example--create-python-index () |
| 189 | "Interface function for imenu package to find all python classes and |
| 190 | functions/methods. Calls function |
| 191 | \\[imenu-example--create-python-index-engine]. See that function for |
| 192 | the details of how this works." |
| 193 | (setq imenu-example--python-generic-regexp |
| 194 | (car imenu-example--generic-python-expression)) |
| 195 | (setq imenu-example--python-generic-parens |
| 196 | (if imenu-example--python-show-method-args-p |
| 197 | imenu-example--python-method-arg-parens |
| 198 | imenu-example--python-method-no-arg-parens)) |
| 199 | (goto-char (point-min)) |
| 200 | (imenu-example--create-python-index-engine nil)) |
| 201 | |
| 202 | (defun imenu-example--create-python-index-engine (&optional start-indent) |
| 203 | "Function for finding all definitions (classes, methods, or functions) |
| 204 | in a python file for the imenu package. |
| 205 | |
| 206 | Retuns a possibly nested alist of the form \(INDEX-NAME |
| 207 | INDEX-POSITION). The second element of the alist may be an alist, |
| 208 | producing a nested list as in \(INDEX-NAME . INDEX-ALIST). |
| 209 | |
| 210 | This function should not be called directly, as it calls itself |
| 211 | recursively and requires some setup. Rather this is the engine for the |
| 212 | function \\[imenu-example--create-python-index]. |
| 213 | |
| 214 | It works recursively by looking for all definitions at the current |
| 215 | indention level. When it finds one, it adds it to the alist. If it |
| 216 | finds a definition at a greater indentation level, it removes the |
| 217 | previous definition from the alist. In it's place it adds all |
| 218 | definitions found at the next indentation level. When it finds a |
| 219 | definition that is less indented then the current level, it retuns the |
| 220 | alist it has created thus far. |
| 221 | |
| 222 | The optional argument START-INDENT indicates the starting indentation |
| 223 | at which to continue looking for python classes, methods, or |
| 224 | functions. If this is not supplied, the function uses the indentation |
| 225 | of the first definition found. " |
| 226 | (let ((index-alist '()) |
| 227 | (sub-method-alist '()) |
| 228 | looking-p |
| 229 | def-name prev-name |
| 230 | cur-indent def-pos |
| 231 | (class-paren (first imenu-example--python-generic-parens)) |
| 232 | (def-paren (second imenu-example--python-generic-parens))) |
| 233 | (setq looking-p |
| 234 | (re-search-forward imenu-example--python-generic-regexp |
| 235 | (point-max) t)) |
| 236 | (while looking-p |
| 237 | (save-excursion |
| 238 | ;; used to set def-name to this value but generic-extract-name is |
| 239 | ;; new to imenu-1.14. this way it still works with imenu-1.11 |
| 240 | ;;(imenu--generic-extract-name imenu-example--python-generic-parens)) |
| 241 | (let ((cur-paren (if (match-beginning class-paren) |
| 242 | class-paren def-paren))) |
| 243 | (setq def-name |
| 244 | (buffer-substring (match-beginning cur-paren) |
| 245 | (match-end cur-paren)))) |
| 246 | (beginning-of-line) |
| 247 | (setq cur-indent (current-indentation))) |
| 248 | |
| 249 | ;; HACK: want to go to the correct definition location. Assuming |
| 250 | ;; here that there are only two..which is true for python. |
| 251 | (setq def-pos |
| 252 | (or (match-beginning class-paren) |
| 253 | (match-beginning def-paren))) |
| 254 | |
| 255 | ; if we don't have a starting indent level, take this one |
| 256 | (or start-indent |
| 257 | (setq start-indent cur-indent)) |
| 258 | |
| 259 | ; if we don't have class name yet, take this one |
| 260 | (or prev-name |
| 261 | (setq prev-name def-name)) |
| 262 | |
| 263 | ;; what level is the next definition on? |
| 264 | ;; must be same, deeper or shallower indentation |
| 265 | (cond |
| 266 | |
| 267 | ;; at the same indent level, add it to the list... |
| 268 | ((= start-indent cur-indent) |
| 269 | (push (cons def-name def-pos) index-alist)) |
| 270 | |
| 271 | ;; deeper indented expression, recur... |
| 272 | ((< start-indent cur-indent) |
| 273 | |
| 274 | ;; the point is currently on the expression we're supposed to |
| 275 | ;; start on, so go back to the last expression. The recursive |
| 276 | ;; call will find this place again and add it to the correct |
| 277 | ;; list |
| 278 | (re-search-backward imenu-example--python-generic-regexp |
| 279 | (point-min) 'move) |
| 280 | (setq sub-method-alist (imenu-example--create-python-index-engine |
| 281 | cur-indent)) |
| 282 | |
| 283 | (if sub-method-alist |
| 284 | ;; we put the last element on the index-alist on the start |
| 285 | ;; of the submethod alist so the user can still get to it. |
| 286 | (let ((save-elmt (pop index-alist))) |
| 287 | (push (cons (imenu-create-submenu-name prev-name) |
| 288 | (cons save-elmt sub-method-alist)) |
| 289 | index-alist)))) |
| 290 | |
| 291 | ;; found less indented expression, we're done. |
| 292 | (t |
| 293 | (setq looking-p nil) |
| 294 | (re-search-backward imenu-example--python-generic-regexp |
| 295 | (point-min) t))) |
| 296 | (setq prev-name def-name) |
| 297 | (and looking-p |
| 298 | (setq looking-p |
| 299 | (re-search-forward imenu-example--python-generic-regexp |
| 300 | (point-max) 'move)))) |
| 301 | (nreverse index-alist))) |
| 302 | |
| 303 | (provide 'pyimenu) |
| 304 | |
| 305 | ;;; PyImenu.EL ends here |