Skip to content

Commit 7d9aabb

Browse files
committed
Improve support for Imenu
Declarations are parsed with a custom parser rather than regexp. It is two customizable styles, `nested` or `flat`. Speedbar is also supported.
1 parent 2aeef85 commit 7d9aabb

File tree

2 files changed

+345
-24
lines changed

2 files changed

+345
-24
lines changed

swift-mode-imenu.el

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
;;; swift-mode-imenu.el --- Major-mode for Apple's Swift programming language, , Imenu -*- lexical-binding: t -*-
2+
3+
;; Copyright (C) 2019 taku0
4+
5+
;; Authors: taku0 (http://github.com/taku0)
6+
;;
7+
;; Version: 7.1.0
8+
;; Package-Requires: ((emacs "24.4") (seq "2.3"))
9+
;; Keywords: languages swift
10+
11+
;; This file is not part of GNU Emacs.
12+
13+
;; This program is free software: you can redistribute it and/or modify
14+
;; it under the terms of the GNU General Public License as published by
15+
;; the Free Software Foundation, either version 3 of the License, or
16+
;; (at your option) any later version.
17+
18+
;; This program is distributed in the hope that it will be useful,
19+
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21+
;; GNU General Public License for more details.
22+
23+
;; You should have received a copy of the GNU General Public License
24+
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
25+
26+
;;; Commentary:
27+
28+
;; List declarations for Imenu
29+
30+
;;; Code:
31+
32+
(require 'swift-mode-lexer)
33+
34+
;;;###autoload
35+
(defgroup swift-mode:imenu nil
36+
"Imenu."
37+
:group 'swift)
38+
39+
;;;###autoload
40+
(defcustom swift-mode:imenu-style
41+
'nested
42+
"Style of Imenu hierarchy.
43+
44+
Values:
45+
46+
- `nested': Class and its members are organized as trees.
47+
- `flat': Organized into a flat list of fully qualified names."
48+
:type '(choice (const :tag "Nested" nested)
49+
(const :tag "Flat" flat))
50+
:group 'swift-mode:imenu
51+
:safe 'symbolp)
52+
53+
(defun swift-mode:declaration (type name-token children)
54+
"Construct and return a declaration.
55+
56+
TYPE is the type of the declaration such as `class' or `struct'.
57+
NAME-TOKEN is the name token of the declaration. For declarations like `init',
58+
it is the keyword token itself.
59+
CHILDREN is the child declarations if exists."
60+
(list type name-token children))
61+
62+
(defun swift-mode:declaration:type (declaration)
63+
"Return the type of DECLARATION."
64+
(nth 0 declaration))
65+
66+
(defun swift-mode:declaration:name-token (declaration)
67+
"Return the name token of DECLARATION."
68+
(nth 1 declaration))
69+
70+
(defun swift-mode:declaration:children (declaration)
71+
"Return the children of DECLARATION."
72+
(nth 2 declaration))
73+
74+
75+
(defun swift-mode:imenu-create-index (&optional style)
76+
"Create an index alist of the current buffer for Imenu.
77+
78+
STYLE is either `nested' or `flat', defaults to `nested'.
79+
If it is `nested', class and its members are organized as trees.
80+
If it is `flat', declarations are organized into a flat list of fully qualified
81+
names."
82+
(unless style (setq style swift-mode:imenu-style))
83+
(save-excursion
84+
(goto-char (point-min))
85+
86+
(let ((declarations '())
87+
(customization-item (list
88+
"*Customize*"
89+
0
90+
(lambda (_name _position)
91+
(customize-group 'swift-mode:imenu)))))
92+
(while (not (eq (swift-mode:token:type
93+
(save-excursion (swift-mode:forward-token)))
94+
'outside-of-buffer))
95+
(setq declarations
96+
(append (swift-mode:scan-declarations) declarations)))
97+
(append (if (eq style 'flat)
98+
(swift-mode:format-for-imenu:flat (nreverse declarations))
99+
(swift-mode:format-for-imenu:nested (nreverse declarations)))
100+
(list customization-item)))))
101+
102+
(defun swift-mode:scan-declarations ()
103+
"Scan declarations from current point.
104+
105+
Return found declarations in reverse order."
106+
(let (next-token
107+
next-type
108+
next-text
109+
name-token
110+
last-class-token
111+
(done nil)
112+
(declarations '()))
113+
(while (not done)
114+
(setq next-token
115+
(swift-mode:forward-token-or-list-except-curly-bracket))
116+
(setq next-type (swift-mode:token:type next-token))
117+
(setq next-text (swift-mode:token:text next-token))
118+
119+
(cond
120+
((equal next-text "class")
121+
;; "class" token may be either a class declaration keyword or a
122+
;; modifier:
123+
;;
124+
;; // Nested class named "final"
125+
;; class Foo { class final {} }
126+
;;
127+
;; // Non-overridable class method named "foo"
128+
;; class Foo { class final func foo() {} }
129+
;;
130+
;; So delays until "{" token.
131+
(setq last-class-token next-token))
132+
133+
((memq next-type '(\; implicit-\; { } outside-of-buffer))
134+
(when (memq next-type '(} outside-of-buffer))
135+
(setq done t))
136+
(cond
137+
;; Having pending "class" token
138+
(last-class-token
139+
(save-excursion
140+
(goto-char (swift-mode:token:end last-class-token))
141+
(setq name-token (swift-mode:forward-token)))
142+
(setq last-class-token nil)
143+
(when (eq (swift-mode:token:type name-token) 'identifier)
144+
(push
145+
(swift-mode:declaration
146+
'class
147+
name-token
148+
(when (eq (swift-mode:token:type next-token) '{)
149+
(nreverse (swift-mode:scan-declarations))))
150+
declarations)))
151+
152+
;; Closure or other unknown block
153+
((eq next-type '{)
154+
(goto-char (swift-mode:token:start next-token))
155+
(swift-mode:forward-token-or-list))
156+
157+
;; Ignores the token otherwise.
158+
))
159+
160+
((member next-text '("struct" "protocol" "extension" "enum"))
161+
(setq last-class-token nil)
162+
(let ((declaration
163+
(swift-mode:scan-declarations:handle-struct-like next-token)))
164+
(when declaration
165+
(push declaration declarations))))
166+
167+
((equal next-text "case")
168+
(setq last-class-token nil)
169+
(let ((case-declarations
170+
(swift-mode:scan-declarations:handle-case-or-variable 'case)))
171+
(setq declarations (append case-declarations declarations))))
172+
173+
((member next-text '("typealias" "associatedtype"))
174+
(setq last-class-token nil)
175+
(setq name-token
176+
(swift-mode:forward-token-or-list-except-curly-bracket))
177+
(when (eq (swift-mode:token:type name-token) 'identifier)
178+
(push
179+
(swift-mode:declaration (intern next-text) name-token nil)
180+
declarations)))
181+
182+
((equal next-text "func")
183+
(setq last-class-token nil)
184+
(setq name-token
185+
(swift-mode:forward-token-or-list-except-curly-bracket))
186+
;; TODO parse parameter names?
187+
(when (memq (swift-mode:token:type name-token) '(identifier operator))
188+
(push
189+
(swift-mode:declaration 'func name-token nil)
190+
declarations)))
191+
192+
((member next-text '("init" "deinit" "subscript"))
193+
(setq last-class-token nil)
194+
(push
195+
(swift-mode:declaration (intern next-text) next-token nil)
196+
declarations))
197+
198+
((member next-text '("let" "var"))
199+
(setq last-class-token nil)
200+
(let ((variable-declarations
201+
(swift-mode:scan-declarations:handle-case-or-variable
202+
(intern next-text))))
203+
(setq declarations (append variable-declarations declarations))))
204+
205+
((member next-text '("prefix" "postfix" "infix"))
206+
(setq last-class-token nil)
207+
(setq next-token
208+
(swift-mode:forward-token-or-list-except-curly-bracket))
209+
(when (equal (swift-mode:token:text next-token) "operator")
210+
(setq name-token
211+
(swift-mode:forward-token-or-list-except-curly-bracket))
212+
(when (eq (swift-mode:token:type name-token) 'operator)
213+
(push
214+
(swift-mode:declaration 'operator name-token nil)
215+
declarations))))
216+
217+
((equal next-text "precedencegroup")
218+
(setq last-class-token nil)
219+
(setq name-token
220+
(swift-mode:forward-token-or-list-except-curly-bracket))
221+
(when (eq (swift-mode:token:type name-token) 'identifier)
222+
(push
223+
(swift-mode:declaration 'precedencegroup name-token nil)
224+
declarations)))))
225+
declarations))
226+
227+
(defun swift-mode:forward-token-or-list-except-curly-bracket ()
228+
"Move point to the end position of the next token or list.
229+
230+
Curly brackets are not regarded as a list.
231+
Return the token skipped."
232+
(let ((next-token (swift-mode:forward-token)))
233+
(if (or (memq (swift-mode:token:type next-token) '(\( \[))
234+
(equal (swift-mode:token:text next-token) "<"))
235+
(progn
236+
(goto-char (swift-mode:token:start next-token))
237+
(swift-mode:forward-token-or-list))
238+
next-token)))
239+
240+
(defun swift-mode:scan-declarations:handle-struct-like (keyword-token)
241+
"Parse struct-like declaration.
242+
243+
Return a declaration if it have a name. Return nil otherwise.
244+
KEYWORD-TOKEN is the keyword beginning the declaration like \"struct\" or
245+
\"enum\"."
246+
(let (next-token
247+
(name-token (swift-mode:forward-token)))
248+
(when (eq (swift-mode:token:type name-token) 'identifier)
249+
(while (progn
250+
(setq next-token
251+
(swift-mode:forward-token-or-list-except-curly-bracket))
252+
(not (memq (swift-mode:token:type next-token)
253+
'(\; implicit-\; { } outside-of-buffer)))))
254+
(swift-mode:declaration
255+
(intern (swift-mode:token:text keyword-token))
256+
name-token
257+
(when (eq (swift-mode:token:type next-token) '{)
258+
(nreverse (swift-mode:scan-declarations)))))))
259+
260+
(defun swift-mode:scan-declarations:handle-case-or-variable (type)
261+
"Parse enum-case, let, or var.
262+
263+
Return a list of declarations.
264+
TYPE is one of `case', `let', or `var'."
265+
;; case A, B(String), C
266+
;; case A, B = 2, C
267+
;;
268+
;; let x = 1,
269+
;; y = 2,
270+
;; z = 3
271+
;;
272+
;; var x {
273+
;; get {
274+
;; return 1
275+
;; }
276+
;; }
277+
;;
278+
;; var x {
279+
;; willSet {
280+
;; }
281+
;; }
282+
;;
283+
;; let (x, y) = (1, 2) // not supported yet
284+
(let (next-token
285+
(items '()))
286+
(while
287+
(progn
288+
(setq next-token (swift-mode:forward-token-or-list))
289+
(when (eq (swift-mode:token:type next-token) 'identifier)
290+
(push (swift-mode:declaration type next-token nil) items))
291+
(while
292+
(progn
293+
(setq next-token (swift-mode:forward-token-or-list))
294+
(not (memq (swift-mode:token:type next-token)
295+
'(\, \; implicit-\; } outside-of-buffer)))))
296+
(eq (swift-mode:token:type next-token) '\,)))
297+
(when (eq (swift-mode:token:type next-token) '})
298+
(goto-char (swift-mode:token:start next-token)))
299+
items))
300+
301+
(defun swift-mode:format-for-imenu:flat (declarations)
302+
"Convert list of DECLARATIONS to alist for `imenu--index-alist'.
303+
304+
Declarations are organized as trees."
305+
(mapcan
306+
(lambda (declaration)
307+
(let* ((name-token (swift-mode:declaration:name-token declaration))
308+
(name (swift-mode:token:text name-token))
309+
(position (swift-mode:token:start name-token))
310+
(children (swift-mode:declaration:children declaration)))
311+
(cons
312+
(cons name position)
313+
(mapcar
314+
(lambda (pair)
315+
(cons (concat name "." (car pair)) (cdr pair)))
316+
(swift-mode:format-for-imenu:flat children)))))
317+
declarations))
318+
319+
(defun swift-mode:format-for-imenu:nested (declarations)
320+
"Convert list of DECLARATIONS to alist for `imenu--index-alist'.
321+
322+
Declarations are organized as a flat list of fully qualified names."
323+
(mapcar
324+
(lambda (declaration)
325+
(let* ((name-token (swift-mode:declaration:name-token declaration))
326+
(name (swift-mode:token:text name-token))
327+
(position (swift-mode:token:start name-token))
328+
(children (swift-mode:declaration:children declaration)))
329+
(if children
330+
(cons name (cons (cons "self" position)
331+
(swift-mode:format-for-imenu:nested children)))
332+
(cons name position))))
333+
declarations))
334+
335+
(provide 'swift-mode-imenu)
336+
337+
;;; swift-mode-imenu.el ends here

swift-mode.el

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
(require 'swift-mode-font-lock)
3939
(require 'swift-mode-beginning-of-defun)
4040
(require 'swift-mode-repl)
41+
(require 'swift-mode-imenu)
4142

4243
;;;###autoload
4344
(defgroup swift nil
@@ -134,29 +135,6 @@ Signal `scan-error' if it hits opening parentheses."
134135
(swift-mode:token:end token))))
135136
token))
136137

137-
138-
;; Imenu
139-
140-
(defun swift-mode:mk-regex-for-def (keyword)
141-
"Make a regex matching the identifier introduced by KEYWORD."
142-
(concat "\\<" (regexp-quote keyword) "\\>"
143-
"\\s *"
144-
"\\("
145-
"\\(?:" "\\sw" "\\|" "\\s_" "\\)" "+"
146-
"\\)"))
147-
148-
(defconst swift-mode:imenu-generic-expression
149-
(list
150-
(list "Functions" (swift-mode:mk-regex-for-def "func") 1)
151-
(list "Classes" (swift-mode:mk-regex-for-def "class") 1)
152-
(list "Enums" (swift-mode:mk-regex-for-def "enum") 1)
153-
(list "Protocols" (swift-mode:mk-regex-for-def "protocol") 1)
154-
(list "Structs" (swift-mode:mk-regex-for-def "struct") 1)
155-
(list "Extensions" (swift-mode:mk-regex-for-def "extension") 1)
156-
(list "Constants" (swift-mode:mk-regex-for-def "let") 1)
157-
(list "Variables" (swift-mode:mk-regex-for-def "var") 1))
158-
"Value for `imenu-generic-expression' in `swift-mode'.")
159-
160138
;;;###autoload
161139
(define-derived-mode swift-mode prog-mode "Swift"
162140
"Major mode for editing Swift code.
@@ -202,7 +180,7 @@ Signal `scan-error' if it hits opening parentheses."
202180

203181
(add-hook 'post-self-insert-hook #'swift-mode:post-self-insert nil t)
204182

205-
(setq-local imenu-generic-expression swift-mode:imenu-generic-expression)
183+
(setq-local imenu-create-index-function #'swift-mode:imenu-create-index)
206184

207185
(setq-local beginning-of-defun-function #'swift-mode:beginning-of-defun)
208186
(setq-local end-of-defun-function #'swift-mode:end-of-defun)
@@ -219,6 +197,12 @@ Signal `scan-error' if it hits opening parentheses."
219197

220198
;;;###autoload (add-to-list 'auto-mode-alist '("\\.swift\\'" . swift-mode))
221199

200+
;;;###autoload (if (fboundp 'speedbar-add-supported-extension)
201+
;;;###autoload (speedbar-add-supported-extension ".swift")
202+
;;;###autoload (add-hook 'speedbar-load-hook
203+
;;;###autoload (lambda ()
204+
;;;###autoload (speedbar-add-supported-extension ".swift"))))
205+
222206
(provide 'swift-mode)
223207

224208
;;; swift-mode.el ends here

0 commit comments

Comments
 (0)