summaryrefslogtreecommitdiff
path: root/scala-mode-imenu.el
blob: f06a52ea432675e077fecb6b004079dabd1d2dfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
;;; 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