diff options
-rwxr-xr-x | Misc/pyimenu.el | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/Misc/pyimenu.el b/Misc/pyimenu.el new file mode 100755 index 0000000..738de7d --- /dev/null +++ b/Misc/pyimenu.el @@ -0,0 +1,305 @@ +;;; PYIMENU.EL --- + +;; Copyright (C) 1995 Perry A. Stoll + +;; Author: Perry A. Stoll <stoll@atr-sw.atr.co.jp> +;; Created: 12 May 1995 +;; Version: 1.0 +;; Keywords: tools python imenu + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; A copy of the GNU General Public License can be obtained from the +;; Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, +;; USA. + +;;;; COMMENTS + +;; I use the imenu package a lot for looking at Lisp and C/C++ +;; code. When I started using Python, I was dismayed that I couldn't +;; use it to look at Python source. So here's a rough go at it. + +;;;; USAGE + +;; This program is used in conjunction with the imenu package. When +;; you call imenu in python-mode in a python buffer, a list of +;; functions and classes is built. The top level menu has a list of +;; all functions and classes defined at the top indentation +;; level. Classes which have methods defined in them have a submenu +;; which contains all definitions contained in them. Selecting any +;; item will bring you to that point in the file. + +;;;; INSTALLATION + +;; You know the routine: +;; 1) Save this file as pyimenu.el, +;; 2) Place that file somewhere in your emacs load path (maybe ~/emacs +;; or ~/emacs/lisp), +;; 3) Byte compile it (M-x byte-compile-file), +;; 4) Add the following (between "cut here" and "end here") to your +;; ~/.emacs file, +;; 5) Reboot. (joke: DON'T do that, although you'll probably have to +;; either reload your ~/.emacs file or start a new emacs) + +;;--------------cut here------------------------------------------- +;;;; Load the pyimenu index function +;;(autoload 'imenu "imenu" nil t) +;;(autoload 'imenu-example--create-python-index "pyimenu") +;;;; Add the index creation function to the python-mode-hook +;;(add-hook 'python-mode-hook +;; (function +;; (lambda () +;; (setq imenu-create-index-function +;; (function imenu-example--create-python-index))))) +;;----------------end here-------------------------------------------- +;; +;; That is all you need. Of course, the following provides a more +;; useful interface. i.e. this is how I have it set up ;-) +;; +;;----------------optionally cut here---------------------------------- +;;(autoload 'imenu-add-to-menubar "imenu" nil t) +;;(defun my-imenu-install-hook () +;; (imenu-add-to-menubar (format "%s-%s" "IM" mode-name))) +;;(add-hook 'python-mode-hook (function my-imenu-install-hook)) +;;;; Bind imenu to some convenient (?) mouse key. This really lets you +;;;; fly around the buffer. Here it is set to Meta-Shift-Mouse3Click. +;;(global-set-key [M-S-down-mouse-3] (function imenu)) +;;-----------------optionaly end here----------------------------------- + +;;;; CAVEATS/NOTES + +;; 0) I'm not a professional elisp programmer and it shows in the code +;; below. If anyone there has suggestions/changes, I'd love to +;; hear them. I've tried the code out on a bunch of python files +;; from the python-1.1.1 Demo distribution and it worked with +;; them - your mileage may very. +;; +;; 1) You must have the imenu package to use this file. This file +;; works with imenu version 1.11 (the version included with emacs +;; 19.28) and imenu version 1.14; if you have a later version, this +;; may not work with it. +;; +;; 2) This setup assumes you also have python-mode.el, so that it can +;; use the python-mode-hook. It comes with the python distribution. +;; +;; 3) I don't have the Python 1.2 distribution yet, so again, this may +;; not work with that. +;; + +(require 'imenu) + +;;; +;;; VARIABLES: customizable in your .emacs file. +;;; + +(defvar imenu-example--python-show-method-args-p nil + "*When using imenu package with python-mode, whether the arguments of +the function/methods should be printed in the imenu buffer in addition +to the function/method name. If non-nil, args are printed.") + +;;; +;;; VARIABLES: internal use. +;;; +(defvar imenu-example--python-class-regexp + (concat ; <<classes>> + "\\(" ; + "^[ \t]*" ; newline and maybe whitespace + "\\(class[ \t]+[a-zA-Z0-9_]+\\)" ; class name + ; possibly multiple superclasses + "\\([ \t]*\\((\\([a-zA-Z0-9_, \t\n]\\)*)\\)?\\)" + "[ \t]*:" ; and the final : + "\\)" ; >>classes<< + ) + "Regexp for Python classes for use with the imenu package." +) + +(defvar imenu-example--python-method-regexp + (concat ; <<methods and functions>> + "\\(" ; + "^[ \t]*" ; new line and maybe whitespace + "\\(def[ \t]+" ; function definitions start with def + "\\([a-zA-Z0-9_]+\\)" ; name is here + ; function arguments... + "[ \t]*(\\([a-zA-Z0-9_=,\* \t\n]*\\))" + "\\)" ; end of def + "[ \t]*:" ; and then the : + "\\)" ; >>methods and functions<< + ) + "Regexp for Python methods/functions for use with the imenu package." + ) + +(defvar imenu-example--python-method-no-arg-parens '(2 8) + "Indicies into the parenthesis list of the regular expression for +python for use with imenu. Using these values will result in smaller +imenu lists, as arguments to functions are not listed. + +See the variable imenu-example--python-show-method-args-p to for +information") + +(defvar imenu-example--python-method-arg-parens '(2 7) + "Indicies into the parenthesis list of the regular expression for +python for use with imenu. Using these values will result in large +imenu lists, as arguments to functions are listed. + +See the variable imenu-example--python-show-method-args-p to for +information") + +;; Note that in this format, this variable can still be used with the +;; imenu--generic-function. Otherwise, there is no real reason to have +;; it. +(defvar imenu-example--generic-python-expression + (cons + (concat + imenu-example--python-class-regexp + "\\|" ; or... + imenu-example--python-method-regexp + ) + imenu-example--python-method-no-arg-parens) + "Generic Python expression which may be used directly with imenu by +setting the variable imenu-generic-expression to this value. Also, see +the function \\[imenu-example--create-python-index] for an alternate +way of finding the index.") + +;; These next two variables are used when searching for the python +;; class/definitions. Just saving some time in accessing the +;; generic-python-expression, really. +(defvar imenu-example--python-generic-regexp) +(defvar imenu-example--python-generic-parens) + +;;; +;;; CODE: +;;; + +;; Note: +;; At first, I tried using some of the functions supplied by +;; python-mode to navigate through functions and classes, but after a +;; while, I decided dump it. This file is relatively self contained +;; and I liked it that. + +;;;###autoload +(defun imenu-example--create-python-index () + "Interface function for imenu package to find all python classes and +functions/methods. Calls function +\\[imenu-example--create-python-index-engine]. See that function for +the details of how this works." + (setq imenu-example--python-generic-regexp + (car imenu-example--generic-python-expression)) + (setq imenu-example--python-generic-parens + (if imenu-example--python-show-method-args-p + imenu-example--python-method-arg-parens + imenu-example--python-method-no-arg-parens)) + (goto-char (point-min)) + (imenu-example--create-python-index-engine nil)) + +(defun imenu-example--create-python-index-engine (&optional start-indent) +"Function for finding all definitions (classes, methods, or functions) +in a python file for the imenu package. + +Retuns a possibly nested alist of the form \(INDEX-NAME + INDEX-POSITION). The second element of the alist may be an alist, +producing a nested list as in \(INDEX-NAME . INDEX-ALIST). + +This function should not be called directly, as it calls itself +recursively and requires some setup. Rather this is the engine for the +function \\[imenu-example--create-python-index]. + +It works recursively by looking for all definitions at the current +indention level. When it finds one, it adds it to the alist. If it +finds a definition at a greater indentation level, it removes the +previous definition from the alist. In it's place it adds all +definitions found at the next indentation level. When it finds a +definition that is less indented then the current level, it retuns the +alist it has created thus far. + + The optional argument START-INDENT indicates the starting indentation +at which to continue looking for python classes, methods, or +functions. If this is not supplied, the function uses the indentation +of the first definition found. " + (let ((index-alist '()) + (sub-method-alist '()) + looking-p + def-name prev-name + cur-indent def-pos + (class-paren (first imenu-example--python-generic-parens)) + (def-paren (second imenu-example--python-generic-parens))) + (setq looking-p + (re-search-forward imenu-example--python-generic-regexp + (point-max) t)) + (while looking-p + (save-excursion + ;; used to set def-name to this value but generic-extract-name is + ;; new to imenu-1.14. this way it still works with imenu-1.11 + ;;(imenu--generic-extract-name imenu-example--python-generic-parens)) + (let ((cur-paren (if (match-beginning class-paren) + class-paren def-paren))) + (setq def-name + (buffer-substring (match-beginning cur-paren) + (match-end cur-paren)))) + (beginning-of-line) + (setq cur-indent (current-indentation))) + + ;; HACK: want to go to the correct definition location. Assuming + ;; here that there are only two..which is true for python. + (setq def-pos + (or (match-beginning class-paren) + (match-beginning def-paren))) + + ; if we don't have a starting indent level, take this one + (or start-indent + (setq start-indent cur-indent)) + + ; if we don't have class name yet, take this one + (or prev-name + (setq prev-name def-name)) + + ;; what level is the next definition on? + ;; must be same, deeper or shallower indentation + (cond + + ;; at the same indent level, add it to the list... + ((= start-indent cur-indent) + (push (cons def-name def-pos) index-alist)) + + ;; deeper indented expression, recur... + ((< start-indent cur-indent) + + ;; the point is currently on the expression we're supposed to + ;; start on, so go back to the last expression. The recursive + ;; call will find this place again and add it to the correct + ;; list + (re-search-backward imenu-example--python-generic-regexp + (point-min) 'move) + (setq sub-method-alist (imenu-example--create-python-index-engine + cur-indent)) + + (if sub-method-alist + ;; we put the last element on the index-alist on the start + ;; of the submethod alist so the user can still get to it. + (let ((save-elmt (pop index-alist))) + (push (cons (imenu-create-submenu-name prev-name) + (cons save-elmt sub-method-alist)) + index-alist)))) + + ;; found less indented expression, we're done. + (t + (setq looking-p nil) + (re-search-backward imenu-example--python-generic-regexp + (point-min) t))) + (setq prev-name def-name) + (and looking-p + (setq looking-p + (re-search-forward imenu-example--python-generic-regexp + (point-max) 'move)))) + (nreverse index-alist))) + +(provide 'pyimenu) + +;;; PyImenu.EL ends here |