blob: d23f6b4ed6351635af3a448176f80cb75fd53afc [file] [log] [blame]
;===--- swift-mode.el ----------------------------------------------------===;
;
; This source file is part of the Swift.org open source project
;
; Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
; Licensed under Apache License v2.0 with Runtime Library Exception
;
; See http://swift.org/LICENSE.txt for license information
; See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
;
;===----------------------------------------------------------------------===;
(require 'compile)
(unless (fboundp 'prog-mode)
(define-derived-mode prog-mode fundamental-mode "Prog"
"Base mode for other programming language modes"
(setq bidi-paragraph-direction 'left-to-right)
(set
(make-local-variable 'require-final-newline) mode-require-final-newline)
(set
(make-local-variable 'parse-sexp-ignore-comments) t)))
;; Create mode-specific variables
(defcustom swift-basic-offset 2
"Default indentation width for Swift source"
:type 'integer)
;; Create mode-specific tables.
(defvar swift-mode-syntax-table nil
"Syntax table used while in SWIFT mode.")
(defvar swift-font-lock-keywords
(list
;; Comments
'("^#!.*" . font-lock-comment-face)
;; Types
'("\\b[A-Z][a-zA-Z_0-9]*\\b" . font-lock-type-face)
;; Floating point constants
'("\\b[-+]?[0-9]+\.[0-9]+\\b" . font-lock-preprocessor-face)
;; Integer literals
'("\\b[-]?[0-9]+\\b" . font-lock-preprocessor-face)
;; Decl and type keywords
`(,(regexp-opt '("class" "init" "deinit" "extension" "fileprivate" "func"
"import" "let" "protocol" "static" "struct" "subscript"
"typealias" "enum" "var" "lazy" "where"
"private" "public" "internal" "override" "throws"
"open" "associatedtype")
'words) . font-lock-keyword-face)
;; Statements
`(,(regexp-opt '("if" "guard" "in" "else" "for" "do" "repeat" "while" "return"
"break" "continue" "switch" "case" "default"
"throw" "try" "catch")
'words) . font-lock-keyword-face)
;; Expressions
`(,(regexp-opt '("new") 'words) . font-lock-keyword-face)
;; Special Variables
'("self" . font-lock-keyword-face)
;; Variables
'("[a-zA-Z_][a-zA-Z_0-9]*" . font-lock-variable-name-face)
;; Unnamed variables
'("$[0-9]+" . font-lock-variable-name-face)
)
"Syntax highlighting for SWIFT"
)
;; ---------------------- Syntax table ---------------------------
(if (not swift-mode-syntax-table)
(progn
(setq swift-mode-syntax-table (make-syntax-table))
(mapc (function (lambda (n)
(modify-syntax-entry (aref n 0)
(aref n 1)
swift-mode-syntax-table)))
'(
;; whitespace (` ')
[?\f " "]
[?\t " "]
[?\ " "]
;; word constituents (`w')
;; punctuation
[?< "."]
[?> "."]
;; comments
[?/ ". 124"]
[?* ". 23b"]
[?\n ">"]
[?\^m ">"]
;; symbol constituents (`_')
[?_ "_"]
;; punctuation (`.')
;; open paren (`(')
[?\( "())"]
[?\[ "(]"]
[?\{ "(}"]
;; close paren (`)')
[?\) ")("]
[?\] ")["]
[?\} "){"]
;; string quote ('"')
[?\" "\""]
;; escape-syntax characters ('\\')
[?\\ "\\"]
))))
;; --------------------- Abbrev table -----------------------------
(defvar swift-mode-abbrev-table nil
"Abbrev table used while in SWIFT mode.")
(define-abbrev-table 'swift-mode-abbrev-table ())
(defvar swift-mode-map
(let ((keymap (make-sparse-keymap)))
keymap)
"Keymap for `swift-mode'.")
;;;###autoload
(define-derived-mode swift-mode prog-mode "Swift"
"Major mode for editing SWIFT source files.
\\{swift-mode-map}
Runs swift-mode-hook on startup."
:group 'swift
(require 'electric)
(set (make-local-variable 'indent-line-function) 'swift-indent-line)
(set (make-local-variable 'parse-sexp-ignore-comments) t)
(set (make-local-variable 'comment-use-syntax) nil) ;; don't use the syntax table; use our regexp
(set (make-local-variable 'comment-start-skip) "\\(?:/\\)\\(?:/[:/]?\\|[*]+\\)[ \t]*")
(set (make-local-variable 'comment-start) "// ")
(set (make-local-variable 'comment-end) "")
(unless (boundp 'electric-indent-chars)
(defvar electric-indent-chars nil))
(unless (boundp 'electric-pair-pairs)
(defvar electric-pair-pairs nil))
(set (make-local-variable 'electric-indent-chars)
(append "{}()[]:," electric-indent-chars))
(set (make-local-variable 'electric-pair-pairs)
(append '(
;; (?' . ?\') ;; This isn't such a great idea because
;; pairs are detected even in strings and comments,
;; and sometimes an apostrophe is just an apostrophe
(?{ . ?}) (?[ . ?]) (?( . ?)) (?` . ?`)) electric-pair-pairs))
(set (make-local-variable 'electric-layout-rules)
'((?\{ . after) (?\} . before)))
(set (make-local-variable 'font-lock-defaults)
'(swift-font-lock-keywords) ))
(defconst swift-doc-comment-detail-re
(let ((just-space "[ \t\n]*")
(not-just-space ".*?[^ \t\n].*")
(eol "\\(?:$\\)")
(continue "\n\\1"))
(concat "^\\([ \t]*///\\)" not-just-space eol
"\\(?:" continue not-just-space eol "\\)*"
"\\(" continue just-space eol
"\\(?:" continue ".*" eol "\\)*"
"\\)"))
"regexp that finds the non-summary part of a swift doc comment as subexpression 2")
(defun swift-hide-doc-comment-detail ()
"Hide everything but the summary part of doc comments.
Use `M-x hs-show-all' to show them again."
(interactive)
(hs-minor-mode)
(save-excursion
(save-match-data
(goto-char (point-min))
(while (search-forward-regexp swift-doc-comment-detail-re (point-max) :noerror)
(hs-hide-comment-region (match-beginning 2) (match-end 2))
(goto-char (match-end 2))))))
(defvar swift-mode-generic-parameter-list-syntax-table
(let ((s (copy-syntax-table swift-mode-syntax-table)))
(modify-syntax-entry ?\< "(>" s)
(modify-syntax-entry ?\> ")<" s)
s))
(defun swift-skip-comments-and-space ()
"Skip comments and whitespace, returning t"
(while (forward-comment 1))
t)
(defconst swift-identifier-re "\\_<[[:alpha:]_].*?\\_>")
(defun swift-skip-optionality ()
"Hop over any comments, whitespace, and strings
of `!' or `?', returning t unconditionally."
(swift-skip-comments-and-space)
(while (not (zerop (skip-chars-forward "!?")))
(swift-skip-comments-and-space)))
(defun swift-skip-generic-parameter-list ()
"Hop over any comments, whitespace, and, if present, a generic
parameter list, returning t if the parameter list was found and
nil otherwise."
(swift-skip-comments-and-space)
(when (looking-at "<")
(with-syntax-table swift-mode-generic-parameter-list-syntax-table
(ignore-errors (forward-sexp) t))))
(defun swift-skip-re (pattern)
"Hop over any comments and whitespace; then if PATTERN matches
the next characters skip over them, returning t if so and nil
otherwise."
(swift-skip-comments-and-space)
(save-match-data
(when (looking-at pattern)
(goto-char (match-end 0))
t)))
(defun swift-skip-identifier ()
"Hop over any comments, whitespace, and an identifier if one is
present, returning t if so and nil otherwise."
(swift-skip-re swift-identifier-re))
(defun swift-skip-simple-type-name ()
"Hop over a chain of the form identifier
generic-parameter-list? ( `.' identifier generic-parameter-list?
)*, returning t if the initial identifier was found and nil otherwise."
(when (swift-skip-identifier)
(swift-skip-generic-parameter-list)
(swift-skip-re "\\?+")
(when (swift-skip-re "\\.")
(swift-skip-simple-type-name))
t))
(defun swift-skip-type-name ()
"Hop over any comments, whitespace, and the name of a type if
one is present, returning t if so and nil otherwise"
(swift-skip-comments-and-space)
(let ((found nil))
;; repeatedly
(while
(and
;; match a tuple or an identifier + optional generic param list
(cond
((looking-at "[[(]")
(forward-sexp)
(setq found t))
((swift-skip-simple-type-name)
(setq found t)))
;; followed by "->"
(prog1 (swift-skip-re "throws\\|rethrows\\|->")
(swift-skip-re "->") ;; accounts for the throws/rethrows cases on the previous line
(swift-skip-comments-and-space))))
found))
(defun swift-in-string-or-comment ()
"Return non-nil if point is in a string or comment."
(or (nth 3 (syntax-ppss)) (nth 4 (syntax-ppss))))
(defconst swift-body-keyword-re
"\\_<\\(var\\|func\\|init\\|deinit\\|subscript\\)\\_>")
(defun swift-hide-bodies ()
"Hide the bodies of methods, functions, and computed properties.
Use `M-x hs-show-all' to show them again."
(interactive)
(hs-minor-mode)
(save-excursion
(save-match-data
(goto-char (point-min))
(while (search-forward-regexp swift-body-keyword-re (point-max) :noerror)
(when
(and
(not (swift-in-string-or-comment))
(let ((keyword (match-string 0)))
;; parse up to the opening brace
(cond
((equal keyword "deinit") t)
((equal keyword "var")
(and (swift-skip-identifier)
(swift-skip-re ":")
(swift-skip-type-name)))
;; otherwise, there's a parameter list
(t
(and
;; parse the function's base name or operator symbol
(if (equal keyword "func") (forward-symbol 1) t)
;; advance to the beginning of the function
;; parameter list
(progn
(swift-skip-generic-parameter-list)
(swift-skip-comments-and-space)
(equal (char-after) ?\())
;; parse the parameter list and any return type
(swift-skip-type-name)))))
(swift-skip-re "{"))
(hs-hide-block :reposition-at-end))))))
(defun swift-indent-line ()
(interactive)
(let (indent-level target-column)
(save-excursion
(widen)
(setq indent-level (car (syntax-ppss (point-at-bol))))
;; Look at the first non-whitespace to see if it's a close paren
(beginning-of-line)
(skip-syntax-forward " ")
(setq target-column
(if (or (equal (char-after) ?\#) (looking-at "//:")) 0
(* swift-basic-offset
(- indent-level
(cond ((= (char-syntax (or (char-after) ?\X)) ?\))
1)
((save-match-data
(looking-at
"case \\|default *:\\|[a-zA-Z_][a-zA-Z0-9_]*\\(\\s-\\|\n\\)*:\\(\\s-\\|\n\\)*\\(for\\|do\\|\\while\\|switch\\)\\>"))
1)
(t 0))))))
(indent-line-to (max target-column 0)))
(when (< (current-column) target-column)
(move-to-column target-column)))
)
;; Compilation error parsing
(push 'swift0 compilation-error-regexp-alist)
(push 'swift1 compilation-error-regexp-alist)
(push 'swift-fatal compilation-error-regexp-alist)
(push `(swift0
,(concat
"^"
"[ \t]+" "\\(?:(@\\)?"
"[A-Z][A-Za-z0-9_]*@"
;; Filename \1
"\\("
"[0-9]*[^0-9\n]"
"\\(?:"
"[^\n :]" "\\|" " [^/\n]" "\\|" ":[^ \n]"
"\\)*?"
"\\)"
":"
;; Line number (\2)
"\\([0-9]+\\)"
":"
;; Column \3
"\\([0-9]+\\)"
)
1 2 3 0)
compilation-error-regexp-alist-alist)
(push `(swift1
,(concat
"^"
"[0-9]+[.][ \t]+While .* at \\[?"
;; Filename \1
"\\("
"[0-9]*[^0-9\n]"
"\\(?:"
"[^\n :]" "\\|" " [^/\n]" "\\|" ":[^ \n]"
"\\)*?"
"\\)"
":"
;; Line number (\2)
"\\([0-9]+\\)"
":"
;; Column \3
"\\([0-9]+\\)"
)
1 2 3 2)
compilation-error-regexp-alist-alist)
(push `(swift-fatal
,(concat
"^\\(?:assertion failed\\|fatal error\\): \\(?:.*: \\)?file "
;; Filename \1
"\\("
"[0-9]*[^0-9\n]"
"\\(?:"
"[^\n :]" "\\|" " [^/\n]" "\\|" ":[^ \n]"
"\\)*?"
"\\)"
", line "
;; Line number (\2)
"\\([0-9]+\\)"
)
1 2 nil 2)
compilation-error-regexp-alist-alist)
(defgroup swift nil
"Major mode for editing swift source files."
:prefix "swift-")
(provide 'swift-mode)
;; end of swift-mode.el