;;; scala-mode-imenu.el - Major mode for editing scala
;;; Copyright (c) 2014 Heikki Vesalainen
;;; For information on the License, see the LICENSE file
;;; Code:
(require 'scala-mode-syntax)
;; Make lambdas proper clousures (only in this file)
(make-local-variable 'lexical-binding)
(setq lexical-binding t)
(defcustom scala-imenu:should-flatten-index t
"Controls whether or not the imenu index is flattened or hierarchical."
:type 'boolean
:safe #'booleanp
:group 'scala)
(defcustom scala-imenu:build-imenu-candidate
'scala-imenu:default-build-imenu-candidate
"Controls whether or not the imenu index has definition type information."
:type 'function
:group 'scala)
(defcustom scala-imenu:cleanup-hooks nil
"Functions that will be run after the construction of each imenu"
:type 'hook
:group 'scala)
(defun scala-imenu:flatten-list (incoming-list &optional predicate)
(when (not predicate) (setq predicate 'listp))
(cl-mapcan (lambda (x) (if (funcall predicate x)
(scala-imenu:flatten-list x predicate) (list x))) incoming-list))
(defun scala-imenu:flatten-imenu-index (index)
(cl-mapcan (lambda (x) (if (listp (cdr x))
(scala-imenu:flatten-imenu-index (cdr x))
(list x))) index))
(defun scala-imenu:create-imenu-index ()
(let ((imenu-index (cl-mapcar 'scala-imenu:build-imenu-candidates
(scala-imenu:create-index))))
(dolist (cleanup-hook scala-imenu:cleanup-hooks)
(funcall cleanup-hook))
(if scala-imenu:should-flatten-index
(scala-imenu:flatten-imenu-index imenu-index)
imenu-index)))
(defun scala-imenu:build-imenu-candidates (member-info &optional parents)
(if (listp (car member-info))
(let* ((current-member-info (car member-info))
(child-member-infos (cdr member-info))
(current-member-result
(scala-imenu:destructure-for-build-imenu-candidate
current-member-info parents))
(current-member-name (car current-member-result)))
(if child-member-infos
(let ((current-member-members
(scala-imenu:build-child-members
(append parents `(,current-member-info))
(cdr member-info))))
`(,current-member-name .
,(cons current-member-result current-member-members)))
current-member-result))
(scala-imenu:destructure-for-build-imenu-candidate member-info parents)))
(defun scala-imenu:build-child-members (parents child-members)
(cl-mapcar (lambda (child) (scala-imenu:build-imenu-candidates
child parents)) child-members))
(defun scala-imenu:destructure-for-build-imenu-candidate (member-info parents)
(cl-destructuring-bind (member-name definition-type marker)
member-info (funcall scala-imenu:build-imenu-candidate
member-name definition-type marker parents)))
(defun scala-imenu:default-build-imenu-candidate (member-name definition-type
marker parents)
(let* ((all-names
(append (cl-mapcar (lambda (parent) (car parent)) parents)
`(,member-name)))
(member-string (mapconcat 'identity all-names ".")))
`(,(format "(%s)%s" definition-type member-string) . ,marker)))
(defun scala-imenu:create-index ()
(let ((class nil) (index nil))
(goto-char (point-max))
(while (setq class (scala-imenu:parse-nested-from-end))
(setq index (cons class index)))
index))
(defun scala-imenu:parse-nested-from-end ()
(let ((last-point (point)) (class-name nil) (definition-type nil))
(scala-syntax:beginning-of-definition)
;; We're done if scala-syntax:beginning-of-definition has no effect.
(if (eq (point) last-point) nil
(progn (looking-at scala-syntax:all-definition-re)
(setq class-name (match-string-no-properties 2))
(setq definition-type (match-string-no-properties 1)))
`(,`(,class-name ,definition-type ,(point-marker)) .
,(scala-imenu:nested-members)))))
(defun scala-imenu:parse-nested-from-beginning ()
(scala-syntax:end-of-definition)
(scala-imenu:parse-nested-from-end))
(defun scala-imenu:nested-members ()
(let ((start-point (point)))
(save-excursion
(scala-syntax:end-of-definition)
;; This gets us inside of the class definition
;; It seems like there should be a better way
;; to do this.
(backward-char)
(reverse (scala-imenu:get-nested-members start-point)))))
(defvar scala-imenu:nested-definition-types '("class" "object" "trait"))
(defun scala-imenu:get-nested-members (parent-start-point)
(scala-syntax:beginning-of-definition)
(if (< parent-start-point (point))
(cons (scala-imenu:get-member-info-at-point)
(scala-imenu:get-nested-members parent-start-point))
nil))
(defun scala-imenu:get-member-info-at-point ()
(looking-at scala-syntax:all-definition-re)
(let* ((member-name (match-string-no-properties 2))
(definition-type (match-string-no-properties 1)))
(if (member definition-type scala-imenu:nested-definition-types)
(save-excursion (scala-imenu:parse-nested-from-beginning))
`(,member-name ,definition-type ,(point-marker)))))
(provide 'scala-mode-imenu)
;;; scala-mode-imenu.el ends here