+;;; js2-imenu-extras.el --- Imenu support for additional constructs\r
+\r
+;; Author: Dmitry Gutov <dgutov@yandex.ru>\r
+;; Keywords: languages, javascript, imenu\r
+\r
+;; This file is part of GNU Emacs.\r
+\r
+;; GNU Emacs is free software: you can redistribute it and/or modify\r
+;; it under the terms of the GNU General Public License as published by\r
+;; the Free Software Foundation, either version 3 of the License, or\r
+;; (at your option) any later version.\r
+\r
+;; GNU Emacs is distributed in the hope that it will be useful,\r
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+;; GNU General Public License for more details.\r
+\r
+;; You should have received a copy of the GNU General Public License\r
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.\r
+\r
+;;; Commentary:\r
+\r
+;; This package adds Imenu support for additional framework constructs and\r
+;; general patterns to `js2-mode'.\r
+\r
+;; Usage:\r
+\r
+;; (eval-after-load 'js2-mode\r
+;; '(progn\r
+;; (require 'js2-imenu-extras)\r
+;; (js2-imenu-extras-setup)))\r
+\r
+;; To customize how it works:\r
+;; M-x customize-group RET js2-imenu RET\r
+\r
(eval-when-compile\r
(require 'cl))\r
\r
+(require 'js2-mode)\r
+\r
(defconst js2-imenu-extension-styles\r
`((:framework jquery\r
:call-re "\\_<\\(?:jQuery\\|\\$\\|_\\)\\.extend\\s-*("\r
\r
(:framework backbone\r
:call-re ,(concat "\\_<" js2-mode-identifier-re "\\.extend\\s-*(")\r
- :recorder js2-imenu-record-backbone-extend)))\r
+ :recorder js2-imenu-record-backbone-extend))\r
+ "List of JavaScript class definition or extension styles.\r
+\r
+:framework is a valid value in `js2-imenu-enabled-frameworks'.\r
+\r
+:call-re is a regular expression that has no capturing groups.\r
+\r
+:recorder is a function name that will be called when the regular\r
+expression matches some text in the buffer. When it's called, point will be\r
+at the end of the match. The function must keep the point position.")\r
\r
(defconst js2-imenu-available-frameworks\r
(mapcar (lambda (style) (plist-get style :framework)) js2-imenu-extension-styles)\r
"Frameworks to be recognized by `js2-mode'."\r
:type (cons 'set (mapcar (lambda (x) (list 'const x))\r
js2-imenu-available-frameworks))\r
- :group 'js2-mode)\r
+ :group 'js2-imenu)\r
\r
(defcustom js2-imenu-show-other-functions t\r
"Non-nil to show functions not recognized by other mechanisms,\r
in a shared namespace."\r
:type 'boolean\r
- :group 'js2-mode)\r
+ :group 'js2-imenu)\r
\r
(defcustom js2-imenu-other-functions-ns "?"\r
"Namespace name to use for other functions."\r
:type 'string\r
- :group 'js2-mode)\r
+ :group 'js2-imenu)\r
+\r
+(defcustom js2-imenu-show-module-pattern t\r
+ "Non-nil to recognize the module pattern:\r
+\r
+var foobs = (function(a) {\r
+ return {fib: function() {}, fub: function() {}};\r
+})(b);\r
+\r
+We record the returned hash as belonging to the named module, and\r
+prefix any functions defined inside the IIFE with the module name."\r
+ :type 'boolean\r
+ :group 'js2-imenu)\r
\r
+;;;###autoload\r
(defun js2-imenu-extras-setup ()\r
(when js2-imenu-enabled-frameworks\r
(add-to-list 'js2-post-parse-callbacks 'js2-imenu-record-declarations t))\r
- (when js2-imenu-show-other-functions\r
- (add-to-list 'js2-post-parse-callbacks 'js2-imenu-record-hashes t)))\r
+ (when (or js2-imenu-show-other-functions js2-imenu-show-module-pattern)\r
+ (add-to-list 'js2-post-parse-callbacks 'js2-imenu-walk-ast t)))\r
\r
(declare (special root))\r
\r
(concat "\\(" (plist-get style :call-re) "\\)"))\r
styles "\\|"))\r
;; Dynamic scoping. Ew.\r
- (js2-mode-ast root)\r
- chains)\r
+ (js2-mode-ast root))\r
(goto-char (point-min))\r
- (while (js-re-search-forward re nil t)\r
- (push (loop for i from 0 to (1- (length styles))\r
- when (match-beginning (1+ i))\r
- return (funcall (plist-get (nth i styles) :recorder)))\r
- chains))\r
- chains))\r
+ (while (js2-re-search-forward re nil t)\r
+ (loop for i from 0 to (1- (length styles))\r
+ when (match-beginning (1+ i))\r
+ return (funcall (plist-get (nth i styles) :recorder))))))\r
\r
(defun js2-imenu-record-jquery-extend ()\r
(let ((pred (lambda (subject)\r
(js2-compute-nested-prop-get subject)\r
(js2-node-abs-pos methods)))))))\r
\r
-(defun js2-imenu-record-hashes ()\r
+(defun js2-imenu-walk-ast ()\r
(js2-visit-ast\r
root\r
(lambda (node end-p)\r
(unless end-p\r
- (if (and (js2-object-prop-node-p node)\r
- (js2-function-node-p (js2-object-prop-node-right node)))\r
- (let ((fn-node (js2-object-prop-node-right node)))\r
- (unless (and js2-imenu-function-map\r
- (gethash fn-node js2-imenu-function-map))\r
- (let ((key-node (js2-object-prop-node-left node)))\r
- (js2-record-imenu-entry fn-node\r
- (list js2-imenu-other-functions-ns\r
- (js2-prop-node-name key-node))\r
- (js2-node-abs-pos key-node))))\r
- nil)\r
- t)))))\r
+ (cond\r
+ ((and js2-imenu-show-other-functions\r
+ (js2-object-prop-node-p node))\r
+ (js2-imenu-record-orphan-function node))\r
+ ((and js2-imenu-show-module-pattern\r
+ (js2-assign-node-p node))\r
+ (js2-imenu-record-module-pattern node)))\r
+ t))))\r
+\r
+(defun js2-imenu-record-orphan-function (node)\r
+ "Record orphan function when it's the value of NODE.\r
+NODE must be `js2-object-prop-node'."\r
+ (when (js2-function-node-p (js2-object-prop-node-right node))\r
+ (let ((fn-node (js2-object-prop-node-right node)))\r
+ (unless (and js2-imenu-function-map\r
+ (gethash fn-node js2-imenu-function-map))\r
+ (let ((key-node (js2-object-prop-node-left node)))\r
+ (js2-record-imenu-entry fn-node\r
+ (list js2-imenu-other-functions-ns\r
+ (js2-prop-node-name key-node))\r
+ (js2-node-abs-pos key-node)))))))\r
+\r
+(defun js2-imenu-record-module-pattern (node)\r
+ "Recognize and record module pattern use instance.\r
+NODE must be `js2-assign-node'."\r
+ (let ((init (js2-assign-node-right node)))\r
+ (when (js2-call-node-p init)\r
+ (let ((target (js2-assign-node-left node))\r
+ (callt (js2-call-node-target init)))\r
+ ;; Just basic call form: (function() {...})();\r
+ ;; TODO: Handle variations without duplicating `js2-wrapper-function-p'?\r
+ (when (and (js2-paren-node-p callt)\r
+ (js2-function-node-p (js2-paren-node-expr callt)))\r
+ (let* ((fn (js2-paren-node-expr callt))\r
+ (blk (js2-function-node-body fn))\r
+ (ret (car (last (js2-block-node-kids blk)))))\r
+ (when (and (js2-return-node-p ret)\r
+ (js2-object-node-p (js2-return-node-retval ret)))\r
+ ;; TODO: Map function names when revealing module pattern is used.\r
+ (let ((retval (js2-return-node-retval ret))\r
+ (target-qname (js2-compute-nested-prop-get target)))\r
+ (js2-record-object-literal retval target-qname\r
+ (js2-node-abs-pos retval))\r
+ (js2-record-imenu-entry fn target-qname\r
+ (js2-node-abs-pos target))))))))))\r
\r
(provide 'js2-imenu-extras)\r