;;; php-imenu.el --- object-oriented, hierarchical imenu for PHP ;;; ;;; Maintainer: Marcel Cary ;;; Keywords: php languages oop ;;; Created: 2008-06-23 ;;; Modified: 2008-07-18 ;;; X-URL: http://www.oak.homeunix.org/~marcel/blog/articles/2008/07/14/nested-imenu-for-php ;;; ;;; Copyright (C) 2008 Marcel Cary ;;; ;;; License ;;; ;;; 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 ;;; of the License, 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. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software ;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;;; ;;; ;;; Usage ;;; ;;; Rename this file to php-imenu.el if it isn't already then place it in ;;; your Emacs lisp path (eg. site-lisp) and add to your .emacs file: ;;; ;;;--------------cut here------------------------------------------- ; ;; Load the php-imenu index function ; (autoload 'php-imenu-create-index "php-imenu" nil t) ; ;; Add the index creation function to the php-mode-hook ; (add-hook 'php-mode-user-hook 'php-imenu-setup) ; (defun php-imenu-setup () ; (setq imenu-create-index-function (function php-imenu-create-index)) ; ;; uncomment if you prefer speedbar: ; ;(setq php-imenu-alist-postprocessor (function reverse)) ; (imenu-add-menubar-index) ; ) ;;;----------------end here-------------------------------------------- ;;; ;;; Commentary ;;; ;;; Refines php-mode's imenu support. Imenu provides a menubar entry called ;;; "Index" that allows you to jump to a structural element of a file. While ;;; php-mode generates separate lists of functions and classes in imenu, ;;; php-imenu.el (this code) generates a tree of class and function ;;; definitions. It lists functions under the classes in which they're ;;; defined. The hierarchical display of functions within their classes makes ;;; the "Index" menu far more useful in understanding the high-level structure ;;; of a file, and it makes it easier to find a method when a file contains ;;; multiple by the same name. ;;; ;;; Code: (eval-when-compile (require 'cl)) (require 'imenu) (require 'thingatpt) ;;; Alas, speedbar shows menu items in reverse, but only below the top level. ;;; Provide a way to fix it. See sample configuration in file comment. (defvar php-imenu-alist-postprocessor (function identity)) ;;; Want to see properties or defines? Add an entry for them here. (defvar php-imenu-patterns nil) (setq php-imenu-patterns (list ;; types: classes and interfaces (list ;; for some reason [:space:] and \s- aren't matching \n (concat "^\\s-*" "\\(\\(abstract[[:space:]\n]+\\)?class\\|interface\\)" "[[:space:]\n]+" "\\([a-zA-Z0-9_]+\\)[[:space:]\n]*" ; class/iface name "\\([a-zA-Z0-9_[:space:]\n]*\\)" ; extends / implements clauses "[{]") (lambda () (message "%S %S" (match-string-no-properties 3) (match-string-no-properties 1)) (concat (match-string-no-properties 3) " - " (match-string-no-properties 1))) (lambda () (save-excursion (backward-up-list 1) (forward-sexp) (point)))) ;; functions (list (concat "^[[:space:]\n]*" "\\(\\(public\\|protected\\|private\\|" "static\\|abstract\\)[[:space:]\n]+\\)*" "function[[:space:]\n]*&?[[:space:]\n]*" "\\([a-zA-Z0-9_]+\\)[[:space:]\n]*" ; function name "[(]") (lambda () (concat (match-string-no-properties 3) "()")) (lambda () (save-excursion (backward-up-list 1) (forward-sexp) (when (not (looking-at "\\s-*;")) (forward-sexp)) (point)))) )) ;;; Global variable to pass to imenu-progress-message in multiple functions (defvar php-imenu-prev-pos nil) ;;; An implementation of imenu-create-index-function (defun php-imenu-create-index () (let (prev-pos) (imenu-progress-message php-imenu-prev-pos 0) (let ((result (php-imenu-create-index-helper (point-min) (point-max) nil))) ;(message "bye %S" result) (imenu-progress-message php-imenu-prev-pos 100) result))) (defun php-imenu-create-index-helper (min max name) (let ((combined-pattern (concat "\\(" (mapconcat (function (lambda (pat) (first pat))) php-imenu-patterns "\\)\\|\\(") "\\)")) (index-alist '())) (goto-char min) (save-match-data (while (re-search-forward combined-pattern max t) (let ((pos (set-marker (make-marker) (match-beginning 0))) (min (match-end 0)) (pat (save-excursion (goto-char (match-beginning 0)) (find-if (function (lambda (pat) (looking-at (first pat)))) php-imenu-patterns)))) (when (not pat) (message "php-imenu: How can no pattern get us here! %S" pos)) (when (and pat (not (php-imenu-in-string-p)) ) (let* ((name (funcall (second pat))) (max (funcall (third pat))) (children (php-imenu-create-index-helper min max name))) ;; should validate max: what happens if unmatched curly? ;(message "%S %S %S" nm name (mapcar (function first) children)) (if (equal '() children) (push (cons name pos) index-alist) (push (cons name (funcall php-imenu-alist-postprocessor (cons (cons "*go*" pos) children))) index-alist)) )) (imenu-progress-message php-imenu-prev-pos nil) ))) (reverse index-alist))) ;;; Recognize when in quoted strings or heredoc-style string literals (defun php-imenu-in-string-p () (save-match-data (or (in-string-p) (let ((pt (point))) (save-excursion (and (re-search-backward "<<<\\([A-Za-z0-9_]+\\)$" nil t) (not (re-search-forward (concat "^" (match-string-no-properties 1) ";$") pt t))))))))