|
| 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 |
0 commit comments