-
-
Notifications
You must be signed in to change notification settings - Fork 862
/
lsp-imenu.el
201 lines (161 loc) · 7.29 KB
/
lsp-imenu.el
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
;; Copyright (C) 2016-2018 Vibhav Pant <vibhavp@gmail.com> -*- lexical-binding: t -*-
;; 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 3 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, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Imenu integration with lsp-mode. Enable with:
;; (require 'lsp-imenu)
;; (add-hook 'lsp-after-open-hook 'lsp-enable-imenu)
;;; Code:
(require 'imenu)
(require 'lsp-methods)
(require 'seq)
(defgroup lsp-imenu nil
"Customization group for `lsp-imenu'."
:group 'lsp-mode)
(defcustom lsp-imenu-show-container-name t
"Display the symbol's container name in an imenu entry."
:type 'boolean
:group 'lsp-imenu)
(defcustom lsp-imenu-container-name-separator "/"
"Separator string to use to separate the container name from the symbol while displaying imenu entries."
:type 'string
:group 'lsp-imenu)
(defcustom lsp-imenu-sort-methods '(kind name)
"How to sort the imenu items.
The value is a list of `kind' `name' or `position'. Priorities
are determined by the index of the element."
:type '(repeat (choice (const name)
(const position)
(const kind))))
(defconst lsp--imenu-compare-function-alist
(list (cons 'name #'lsp--imenu-compare-name)
(cons 'kind #'lsp--imenu-compare-kind)
(cons 'position #'lsp--imenu-compare-position))
"An alist of (METHOD . FUNCTION).
METHOD is one of the symbols accepted by
`lsp-imenu-sort-methods'.
FUNCTION takes two hash tables representing DocumentSymbol. It
returns a negative number, 0, or a positive number indicating
whether the first parameter is less than, equal to, or greater
than the second parameter.")
(define-inline lsp--point-to-marker (p)
(inline-quote (save-excursion (goto-char ,p) (point-marker))))
(defun lsp--symbol-to-imenu-elem (sym)
"Convert SYM to imenu element.
SYM is a SymbolInformation message.
Return a cons cell (full-name . start-point)."
(let* ((start-point (lsp--symbol-get-start-point sym))
(name (gethash "name" sym))
(container (gethash "containerName" sym)))
(cons (if (and lsp-imenu-show-container-name container)
(concat container lsp-imenu-container-name-separator name)
name)
start-point)))
(defun lsp--symbol-to-hierarchical-imenu-elem (sym)
"Convert SYM to hierarchical imenu elements.
SYM is a DocumentSymbol message.
Return cons cell (\"symbol-name (symbol-kind)\" . start-point) if
SYM doesn't have any children. Otherwise return a cons cell with
an alist
(\"symbol-name\" . ((\"(symbol-kind)\" . start-point)
cons-cells-from-children))"
(let* ((start-point (lsp--symbol-get-start-point sym))
(name (gethash "name" sym)))
(if (gethash "children" sym)
(cons name
(cons (cons (format "(%s)" (lsp--get-symbol-type sym)) start-point)
(lsp--imenu-create-hierarchical-index (gethash "children" sym))))
(cons (format "%s (%s)" name (lsp--get-symbol-type sym)) start-point))))
(defun lsp--symbol-get-start-point (sym)
"Get the start point of the name of SYM.
SYM can be either DocumentSymbol or SymbolInformation."
(let* ((location (gethash "location" sym))
(name-range (or (and location (gethash "range" location))
(gethash "selectionRange" sym)))
(start-point (lsp--position-to-point
(gethash "start" name-range))))
(if imenu-use-markers (lsp--point-to-marker start-point) start-point)))
(defun lsp--symbol-filter (sym)
"Determine if SYM is for the current document."
(if-let ((location (gethash "location" sym)))
;; It's a SymbolInformation
(not
(lsp--equal-files
(lsp--uri-to-path (gethash "uri" (gethash "location" sym)))
(buffer-file-name)))
;; It's a DocumentSymbol, which is always in the current buffer file.
nil))
(defun lsp--get-symbol-type (sym)
"The string name of the kind of SYM."
(or (cdr (assoc (gethash "kind" sym) lsp--symbol-kind)) "Other"))
(defun lsp--imenu-create-index ()
"Create imenu index from document symbols."
(let ((symbols (lsp--get-document-symbols)))
(if (lsp--imenu-hierarchical-p symbols)
(lsp--imenu-create-hierarchical-index symbols)
(mapcar (lambda (nested-alist)
(cons (car nested-alist)
(mapcar #'lsp--symbol-to-imenu-elem (cdr nested-alist))))
(seq-group-by #'lsp--get-symbol-type (lsp--imenu-filter-symbols symbols))))))
(defun lsp--imenu-filter-symbols (symbols)
"Filter out unsupported symbols from SYMBOLS."
(seq-remove #'lsp--symbol-filter symbols))
(defun lsp--imenu-hierarchical-p (symbols)
"Determine whether any element in SYMBOLS has children."
(seq-some (lambda (sym)
(gethash "children" sym))
symbols))
(defun lsp--imenu-create-hierarchical-index (symbols)
"Create imenu index for hierarchical SYMBOLS.
SYMBOLS are a list of DocumentSymbol messages.
Return a nested alist keyed by symbol names. e.g.
((\"SomeClass\" (\"(Class)\" . 10)
(\"someField (Field)\" . 20)
(\"someFunction (Function)\" . 25)
(\"SomeSubClass\" (\"(Class)\" . 30)
(\"someSubField (Field)\" . 35))
(\"someFunction (Function)\" . 40))"
(let ((symbols (lsp--imenu-filter-symbols symbols)))
(mapcar (lambda (sym)
(lsp--symbol-to-hierarchical-imenu-elem sym))
(sort (lsp--imenu-filter-symbols symbols)
(lambda (sym1 sym2)
(lsp--imenu-symbol-lessp sym1 sym2))))))
(defun lsp--imenu-symbol-lessp (sym1 sym2)
(let* ((compare-results (mapcar (lambda (method)
(funcall (alist-get method lsp--imenu-compare-function-alist)
sym1 sym2))
lsp-imenu-sort-methods))
(result (seq-find (lambda (result)
(not (= result 0)))
compare-results
0)))
(and (numberp result) (< result 0))))
(defun lsp--imenu-compare-kind (sym1 sym2)
(let ((kind1 (gethash "kind" sym1))
(kind2 (gethash "kind" sym2)))
(- kind1 kind2)))
(defun lsp--imenu-compare-position (sym1 sym2)
(let ((position1 (lsp--symbol-get-start-point sym1))
(position2 (lsp--symbol-get-start-point sym2)))
(- position1 position2)))
(defun lsp--imenu-compare-name (sym1 sym2)
(let* ((name1 (gethash "name" sym1))
(name2 (gethash "name" sym2))
(result (compare-strings name1 0 (length name1) name2 0 (length name2))))
(if (numberp result)
result
0)))
(defun lsp-enable-imenu ()
"Use lsp-imenu for the current buffer."
(setq-local imenu-create-index-function #'lsp--imenu-create-index))
(provide 'lsp-imenu)
;;; lsp-imenu.el ends here