diff --git a/Misc/python-mode.el b/Misc/python-mode.el index 7db9f38515a..42b112df3b6 100644 --- a/Misc/python-mode.el +++ b/Misc/python-mode.el @@ -229,6 +229,11 @@ the Emacs bell is also rung as a warning.") )) "Additional expressions to highlight in Python mode.") +(defvar imenu-example--python-show-method-args-p nil + "*Controls echoing of arguments of functions & methods in the imenu buffer. +When non-nil, arguments are printed.") + + ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ;; NO USER DEFINABLE VARIABLES BEYOND THIS POINT @@ -413,6 +418,211 @@ Currently-active file is at the head of the list.") ["Describe mode" py-describe-mode t] ))) + + +;; imenu definitions, courtesy of Perry A. Stoll +(defvar imenu-example--python-class-regexp + (concat ; <> + "\\(" ; + "^[ \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 ; <> + "\\(" ; + "^[ \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 groups of the Python regexp 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' for more +information.") + +(defvar imenu-example--python-method-arg-parens '(2 7) + "Indicies into groups of the Python regexp 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' for more +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. +Used by setting the variable `imenu-generic-expression' to this value. +Also, see the function \\[imenu-example--create-python-index] for a +better alternative for 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) + + +;;;###autoload +(eval-when-compile + ;; Imenu isn't used in XEmacs, so just ignore load errors + (condition-case () + (progn + (require 'cl) + (require 'imenu)) + (error nil))) + +(defun imenu-example--create-python-index () + "Python interface function for imenu package. +Finds 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 imenu definitions in Python. + +Finds all definitions (classes, methods, or functions) in a Python +file for the imenu package. + +Returns 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 next correct definition location. we + ;; explicitly list them here. would be better to have them in a + ;; list. + (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) + + ;; if we don't have push, use the following... + ;;(setf index-alist (cons (cons def-name def-pos) index-alist)) + (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))) + ;;;###autoload (defun python-mode ()