branch: elpa/swift-mode commit 7d9aabbc151b7b8e5abaa0025720eceb2c12394e Author: taku0 <mxxouy6x3m_git...@tatapa.org> Commit: taku0 <mxxouy6x3m_git...@tatapa.org>
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. --- swift-mode-imenu.el | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++ swift-mode.el | 32 ++--- 2 files changed, 345 insertions(+), 24 deletions(-) diff --git a/swift-mode-imenu.el b/swift-mode-imenu.el new file mode 100644 index 0000000..ea72901 --- /dev/null +++ b/swift-mode-imenu.el @@ -0,0 +1,337 @@ +;;; swift-mode-imenu.el --- Major-mode for Apple's Swift programming language, , Imenu -*- lexical-binding: t -*- + +;; Copyright (C) 2019 taku0 + +;; Authors: taku0 (http://github.com/taku0) +;; +;; Version: 7.1.0 +;; Package-Requires: ((emacs "24.4") (seq "2.3")) +;; Keywords: languages swift + +;; This file is not part of GNU Emacs. + +;; 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: + +;; List declarations for Imenu + +;;; Code: + +(require 'swift-mode-lexer) + +;;;###autoload +(defgroup swift-mode:imenu nil + "Imenu." + :group 'swift) + +;;;###autoload +(defcustom swift-mode:imenu-style + 'nested + "Style of Imenu hierarchy. + +Values: + +- `nested': Class and its members are organized as trees. +- `flat': Organized into a flat list of fully qualified names." + :type '(choice (const :tag "Nested" nested) + (const :tag "Flat" flat)) + :group 'swift-mode:imenu + :safe 'symbolp) + +(defun swift-mode:declaration (type name-token children) + "Construct and return a declaration. + +TYPE is the type of the declaration such as `class' or `struct'. +NAME-TOKEN is the name token of the declaration. For declarations like `init', +it is the keyword token itself. +CHILDREN is the child declarations if exists." + (list type name-token children)) + +(defun swift-mode:declaration:type (declaration) + "Return the type of DECLARATION." + (nth 0 declaration)) + +(defun swift-mode:declaration:name-token (declaration) + "Return the name token of DECLARATION." + (nth 1 declaration)) + +(defun swift-mode:declaration:children (declaration) + "Return the children of DECLARATION." + (nth 2 declaration)) + + +(defun swift-mode:imenu-create-index (&optional style) + "Create an index alist of the current buffer for Imenu. + +STYLE is either `nested' or `flat', defaults to `nested'. +If it is `nested', class and its members are organized as trees. +If it is `flat', declarations are organized into a flat list of fully qualified +names." + (unless style (setq style swift-mode:imenu-style)) + (save-excursion + (goto-char (point-min)) + + (let ((declarations '()) + (customization-item (list + "*Customize*" + 0 + (lambda (_name _position) + (customize-group 'swift-mode:imenu))))) + (while (not (eq (swift-mode:token:type + (save-excursion (swift-mode:forward-token))) + 'outside-of-buffer)) + (setq declarations + (append (swift-mode:scan-declarations) declarations))) + (append (if (eq style 'flat) + (swift-mode:format-for-imenu:flat (nreverse declarations)) + (swift-mode:format-for-imenu:nested (nreverse declarations))) + (list customization-item))))) + +(defun swift-mode:scan-declarations () + "Scan declarations from current point. + +Return found declarations in reverse order." + (let (next-token + next-type + next-text + name-token + last-class-token + (done nil) + (declarations '())) + (while (not done) + (setq next-token + (swift-mode:forward-token-or-list-except-curly-bracket)) + (setq next-type (swift-mode:token:type next-token)) + (setq next-text (swift-mode:token:text next-token)) + + (cond + ((equal next-text "class") + ;; "class" token may be either a class declaration keyword or a + ;; modifier: + ;; + ;; // Nested class named "final" + ;; class Foo { class final {} } + ;; + ;; // Non-overridable class method named "foo" + ;; class Foo { class final func foo() {} } + ;; + ;; So delays until "{" token. + (setq last-class-token next-token)) + + ((memq next-type '(\; implicit-\; { } outside-of-buffer)) + (when (memq next-type '(} outside-of-buffer)) + (setq done t)) + (cond + ;; Having pending "class" token + (last-class-token + (save-excursion + (goto-char (swift-mode:token:end last-class-token)) + (setq name-token (swift-mode:forward-token))) + (setq last-class-token nil) + (when (eq (swift-mode:token:type name-token) 'identifier) + (push + (swift-mode:declaration + 'class + name-token + (when (eq (swift-mode:token:type next-token) '{) + (nreverse (swift-mode:scan-declarations)))) + declarations))) + + ;; Closure or other unknown block + ((eq next-type '{) + (goto-char (swift-mode:token:start next-token)) + (swift-mode:forward-token-or-list)) + + ;; Ignores the token otherwise. + )) + + ((member next-text '("struct" "protocol" "extension" "enum")) + (setq last-class-token nil) + (let ((declaration + (swift-mode:scan-declarations:handle-struct-like next-token))) + (when declaration + (push declaration declarations)))) + + ((equal next-text "case") + (setq last-class-token nil) + (let ((case-declarations + (swift-mode:scan-declarations:handle-case-or-variable 'case))) + (setq declarations (append case-declarations declarations)))) + + ((member next-text '("typealias" "associatedtype")) + (setq last-class-token nil) + (setq name-token + (swift-mode:forward-token-or-list-except-curly-bracket)) + (when (eq (swift-mode:token:type name-token) 'identifier) + (push + (swift-mode:declaration (intern next-text) name-token nil) + declarations))) + + ((equal next-text "func") + (setq last-class-token nil) + (setq name-token + (swift-mode:forward-token-or-list-except-curly-bracket)) + ;; TODO parse parameter names? + (when (memq (swift-mode:token:type name-token) '(identifier operator)) + (push + (swift-mode:declaration 'func name-token nil) + declarations))) + + ((member next-text '("init" "deinit" "subscript")) + (setq last-class-token nil) + (push + (swift-mode:declaration (intern next-text) next-token nil) + declarations)) + + ((member next-text '("let" "var")) + (setq last-class-token nil) + (let ((variable-declarations + (swift-mode:scan-declarations:handle-case-or-variable + (intern next-text)))) + (setq declarations (append variable-declarations declarations)))) + + ((member next-text '("prefix" "postfix" "infix")) + (setq last-class-token nil) + (setq next-token + (swift-mode:forward-token-or-list-except-curly-bracket)) + (when (equal (swift-mode:token:text next-token) "operator") + (setq name-token + (swift-mode:forward-token-or-list-except-curly-bracket)) + (when (eq (swift-mode:token:type name-token) 'operator) + (push + (swift-mode:declaration 'operator name-token nil) + declarations)))) + + ((equal next-text "precedencegroup") + (setq last-class-token nil) + (setq name-token + (swift-mode:forward-token-or-list-except-curly-bracket)) + (when (eq (swift-mode:token:type name-token) 'identifier) + (push + (swift-mode:declaration 'precedencegroup name-token nil) + declarations))))) + declarations)) + +(defun swift-mode:forward-token-or-list-except-curly-bracket () + "Move point to the end position of the next token or list. + +Curly brackets are not regarded as a list. +Return the token skipped." + (let ((next-token (swift-mode:forward-token))) + (if (or (memq (swift-mode:token:type next-token) '(\( \[)) + (equal (swift-mode:token:text next-token) "<")) + (progn + (goto-char (swift-mode:token:start next-token)) + (swift-mode:forward-token-or-list)) + next-token))) + +(defun swift-mode:scan-declarations:handle-struct-like (keyword-token) + "Parse struct-like declaration. + +Return a declaration if it have a name. Return nil otherwise. +KEYWORD-TOKEN is the keyword beginning the declaration like \"struct\" or +\"enum\"." + (let (next-token + (name-token (swift-mode:forward-token))) + (when (eq (swift-mode:token:type name-token) 'identifier) + (while (progn + (setq next-token + (swift-mode:forward-token-or-list-except-curly-bracket)) + (not (memq (swift-mode:token:type next-token) + '(\; implicit-\; { } outside-of-buffer))))) + (swift-mode:declaration + (intern (swift-mode:token:text keyword-token)) + name-token + (when (eq (swift-mode:token:type next-token) '{) + (nreverse (swift-mode:scan-declarations))))))) + +(defun swift-mode:scan-declarations:handle-case-or-variable (type) + "Parse enum-case, let, or var. + +Return a list of declarations. +TYPE is one of `case', `let', or `var'." + ;; case A, B(String), C + ;; case A, B = 2, C + ;; + ;; let x = 1, + ;; y = 2, + ;; z = 3 + ;; + ;; var x { + ;; get { + ;; return 1 + ;; } + ;; } + ;; + ;; var x { + ;; willSet { + ;; } + ;; } + ;; + ;; let (x, y) = (1, 2) // not supported yet + (let (next-token + (items '())) + (while + (progn + (setq next-token (swift-mode:forward-token-or-list)) + (when (eq (swift-mode:token:type next-token) 'identifier) + (push (swift-mode:declaration type next-token nil) items)) + (while + (progn + (setq next-token (swift-mode:forward-token-or-list)) + (not (memq (swift-mode:token:type next-token) + '(\, \; implicit-\; } outside-of-buffer))))) + (eq (swift-mode:token:type next-token) '\,))) + (when (eq (swift-mode:token:type next-token) '}) + (goto-char (swift-mode:token:start next-token))) + items)) + +(defun swift-mode:format-for-imenu:flat (declarations) + "Convert list of DECLARATIONS to alist for `imenu--index-alist'. + +Declarations are organized as trees." + (mapcan + (lambda (declaration) + (let* ((name-token (swift-mode:declaration:name-token declaration)) + (name (swift-mode:token:text name-token)) + (position (swift-mode:token:start name-token)) + (children (swift-mode:declaration:children declaration))) + (cons + (cons name position) + (mapcar + (lambda (pair) + (cons (concat name "." (car pair)) (cdr pair))) + (swift-mode:format-for-imenu:flat children))))) + declarations)) + +(defun swift-mode:format-for-imenu:nested (declarations) + "Convert list of DECLARATIONS to alist for `imenu--index-alist'. + +Declarations are organized as a flat list of fully qualified names." + (mapcar + (lambda (declaration) + (let* ((name-token (swift-mode:declaration:name-token declaration)) + (name (swift-mode:token:text name-token)) + (position (swift-mode:token:start name-token)) + (children (swift-mode:declaration:children declaration))) + (if children + (cons name (cons (cons "self" position) + (swift-mode:format-for-imenu:nested children))) + (cons name position)))) + declarations)) + +(provide 'swift-mode-imenu) + +;;; swift-mode-imenu.el ends here diff --git a/swift-mode.el b/swift-mode.el index 0ad68d0..e7d2f6b 100644 --- a/swift-mode.el +++ b/swift-mode.el @@ -38,6 +38,7 @@ (require 'swift-mode-font-lock) (require 'swift-mode-beginning-of-defun) (require 'swift-mode-repl) +(require 'swift-mode-imenu) ;;;###autoload (defgroup swift nil @@ -134,29 +135,6 @@ Signal `scan-error' if it hits opening parentheses." (swift-mode:token:end token)))) token)) - -;; Imenu - -(defun swift-mode:mk-regex-for-def (keyword) - "Make a regex matching the identifier introduced by KEYWORD." - (concat "\\<" (regexp-quote keyword) "\\>" - "\\s *" - "\\(" - "\\(?:" "\\sw" "\\|" "\\s_" "\\)" "+" - "\\)")) - -(defconst swift-mode:imenu-generic-expression - (list - (list "Functions" (swift-mode:mk-regex-for-def "func") 1) - (list "Classes" (swift-mode:mk-regex-for-def "class") 1) - (list "Enums" (swift-mode:mk-regex-for-def "enum") 1) - (list "Protocols" (swift-mode:mk-regex-for-def "protocol") 1) - (list "Structs" (swift-mode:mk-regex-for-def "struct") 1) - (list "Extensions" (swift-mode:mk-regex-for-def "extension") 1) - (list "Constants" (swift-mode:mk-regex-for-def "let") 1) - (list "Variables" (swift-mode:mk-regex-for-def "var") 1)) - "Value for `imenu-generic-expression' in `swift-mode'.") - ;;;###autoload (define-derived-mode swift-mode prog-mode "Swift" "Major mode for editing Swift code. @@ -202,7 +180,7 @@ Signal `scan-error' if it hits opening parentheses." (add-hook 'post-self-insert-hook #'swift-mode:post-self-insert nil t) - (setq-local imenu-generic-expression swift-mode:imenu-generic-expression) + (setq-local imenu-create-index-function #'swift-mode:imenu-create-index) (setq-local beginning-of-defun-function #'swift-mode:beginning-of-defun) (setq-local end-of-defun-function #'swift-mode:end-of-defun) @@ -219,6 +197,12 @@ Signal `scan-error' if it hits opening parentheses." ;;;###autoload (add-to-list 'auto-mode-alist '("\\.swift\\'" . swift-mode)) +;;;###autoload (if (fboundp 'speedbar-add-supported-extension) +;;;###autoload (speedbar-add-supported-extension ".swift") +;;;###autoload (add-hook 'speedbar-load-hook +;;;###autoload (lambda () +;;;###autoload (speedbar-add-supported-extension ".swift")))) + (provide 'swift-mode) ;;; swift-mode.el ends here