diff --git a/doc/indentation_logic/.gitignore b/doc/indentation_logic/.gitignore
new file mode 100644
index 0000000..63fe867
--- /dev/null
+++ b/doc/indentation_logic/.gitignore
@@ -0,0 +1 @@
+/pages
diff --git a/doc/indentation_logic/Makefile b/doc/indentation_logic/Makefile
new file mode 100644
index 0000000..5a53c6f
--- /dev/null
+++ b/doc/indentation_logic/Makefile
@@ -0,0 +1,11 @@
+pages.pdf: pages/page_000.pdf
+ pdfunite pages/page_*.pdf pages.pdf
+
+pages/page_000.pdf: pages/page_000.svg
+ for i in pages/page_*.svg ; do inkscape --export-pdf=pages/"$$( basename "$${i}" .svg )".pdf "$${i}" ; done
+
+pages/page_000.svg: src/pages.svg src/split_pages.kts
+ kotlinc-jvm -script src/split_pages.kts src/pages.svg
+
+clean:
+ rm -rf pages.pdf pages
diff --git a/doc/indentation_logic/pages.pdf b/doc/indentation_logic/pages.pdf
new file mode 100644
index 0000000..b0d2fde
Binary files /dev/null and b/doc/indentation_logic/pages.pdf differ
diff --git a/doc/indentation_logic/src/pages.svg b/doc/indentation_logic/src/pages.svg
new file mode 100644
index 0000000..13b414a
--- /dev/null
+++ b/doc/indentation_logic/src/pages.svg
@@ -0,0 +1,3322 @@
+
+
+
+
diff --git a/doc/indentation_logic/src/split_pages.kts b/doc/indentation_logic/src/split_pages.kts
new file mode 100644
index 0000000..a2f1334
--- /dev/null
+++ b/doc/indentation_logic/src/split_pages.kts
@@ -0,0 +1,56 @@
+/**
+ * Write each layer of Inkscape SVG file into separate SVG files.
+ */
+import java.io.File
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+import org.w3c.dom.Element
+
+val INKSCAPE_NS = "http://www.inkscape.org/namespaces/inkscape"
+
+if (args.size < 1) {
+ System.err.println("Usage: kotlinc-jvm -script split_pages.kts pages.svg")
+ System.exit(-1)
+}
+
+val transformer = TransformerFactory.newInstance().newTransformer()
+
+val inputFile = File(args[0])
+val document = DocumentBuilderFactory
+ .newDefaultInstance()
+ .also { it.setNamespaceAware(true) }
+ .newDocumentBuilder()
+ .parse(inputFile)
+
+val svgElement = document.documentElement
+val childNodes = svgElement.childNodes
+val layers = 0.until(childNodes.length)
+ .map { childNodes.item(it) }
+ .filter { child ->
+ child is Element &&
+ child.localName == "g" &&
+ child.getAttributeNS(INKSCAPE_NS, "groupmode") == "layer"
+ }
+
+for (layer in layers) {
+ svgElement.removeChild(layer)
+}
+
+val outputDirectory = File("pages")
+
+outputDirectory.mkdirs()
+
+for ((index, layer) in layers.withIndex()) {
+ svgElement.appendChild(layer)
+
+ // Assuming `style="display:none"`
+ layer.attributes.removeNamedItem("style")
+
+ val outputFile = File(outputDirectory, "page_%03d.svg".format(index))
+
+ transformer.transform(DOMSource(document), StreamResult(outputFile))
+
+ svgElement.removeChild(layer)
+}
diff --git a/kotlin-mode-indent.el b/kotlin-mode-indent.el
new file mode 100644
index 0000000..0ca7802
--- /dev/null
+++ b/kotlin-mode-indent.el
@@ -0,0 +1,1955 @@
+;;; kotlin-mode-indent.el --- Major mode for kotlin, indentation -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 taku0
+
+;; Authors: taku0 (http://github.com/taku0)
+;; Keywords: languages
+;; Package-Requires: ((emacs "24.3"))
+;; Version: 0.0.1
+;; URL: https://github.com/Emacs-Kotlin-Mode-Maintainers/kotlin-mode
+
+;; 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 .
+
+;;; Commentary:
+
+;; Routines for indentation
+
+;;; Code:
+
+(require 'rx)
+(require 'cl-lib)
+
+(require 'kotlin-mode-lexer)
+
+;;; Customizations
+
+(defcustom kotlin-mode--basic-offset 4
+ "Amount of indentation for block contents.
+
+Example:
+
+class Foo {
+ func foo() {} // offset of this line
+}"
+ :type 'integer
+ :group 'kotlin
+ :safe 'integerp)
+
+(defcustom kotlin-mode--parenthesized-expression-offset 4
+ "Amount of indentation inside parentheses and square brackets.
+
+Example:
+
+foo(
+ 1 // offset of this line
+)"
+ :type 'integer
+ :group 'kotlin
+ :safe 'integerp)
+
+(defcustom kotlin-mode--multiline-statement-offset 4
+ "Amount of indentation for continuations of expressions.
+
+Example:
+
+val x = 1 +
+ 2 // offset of this line"
+ :type 'integer
+ :group 'kotlin
+ :safe 'integerp)
+
+(defcustom kotlin-mode--prepend-asterisk-to-comment-line t
+ "Automatically insert a asterisk to each comment line if non-nil.
+
+Example: if the enter key is pressed when the point is after A below,
+
+/*
+ * A
+ */
+
+an asterisk is inserted to the newline:
+
+/*
+ * A
+ *
+ */"
+ :type 'boolean
+ :group 'kotlin
+ :safe 'booleanp)
+
+(defcustom kotlin-mode--insert-space-after-asterisk-in-comment t
+ "Automatically insert a space after asterisk in comment if non-nil.
+
+Example: if an asterisk is inserted before A below,
+
+/*
+A
+ */
+
+a space is inserted after asterisk:
+
+/*
+ * A
+ */"
+ :type 'boolean
+ :group 'kotlin
+ :safe 'booleanp)
+
+(defcustom kotlin-mode--auto-close-multiline-comment t
+ "If non-nil, `indent-new-comment-line' automatically close multiline comment.
+
+Example: when the enter key is pressed after unclosed comment below,
+
+/**
+
+a closing delimiter is inserted automatically:
+
+/**
+ * // cursor is here
+ */"
+ :type 'boolean
+ :group 'kotlin
+ :safe 'booleanp)
+
+(defcustom kotlin-mode--fix-comment-close t
+ "Fix \"* /\" in incomplete multiline comment to \"*/\" if non-nil.
+
+Example:
+
+/*
+ *
+ * // when a slash is inserted here
+
+/*
+ *
+ */ // it become like this
+
+
+/*
+ *
+ * / // rather than like this."
+ :type 'boolean
+ :group 'kotlin
+ :safe 'booleanp)
+
+(defcustom kotlin-mode--break-line-before-comment-close t
+ "If non-nil, break line before the closing delimiter of multiline comments.
+
+Example: if line break is inserted before A below,
+
+/** A */
+
+it becomes like this:
+
+/**
+ * A
+ */
+
+rather than like this:
+
+/**
+ * A */"
+ :type 'boolean
+ :group 'kotlin
+ :safe 'booleanp)
+
+(defcustom kotlin-mode--indent-nonempty-line-in-multiline-string nil
+ "If non-nil, indent nonempty line in multiline string.
+
+`indent-according-to-mode' is no-op otherwise."
+ :type 'boolean
+ :group 'kotlin
+ :safe 'booleanp)
+
+(defcustom kotlin-mode--highlight-anchor nil
+ "Highlight anchor point for indentation if non-nil.
+
+Intended for debugging."
+ :type 'boolean
+ :group 'kotlin
+ :safe 'booleanp)
+
+;;; Constants and variables
+
+(defconst kotlin-mode--statement-parent-tokens
+ '(\( \[ { anonymous-function-parameter-arrow when-expression-arrow
+ bare-else \(\)-before-control-structure-body \; implicit-\;)
+ "Parent tokens for statements.
+
+Parent tokens are tokens before the beginning of statements.")
+
+(defconst kotlin-mode--expression-parent-tokens
+ (append kotlin-mode--statement-parent-tokens
+ '(\, "where" string-chunk-before-template-expression))
+ "Parent tokens for expressions.
+
+Parent tokens are tokens before the beginning of expressions.")
+
+(defvar-local kotlin-mode--anchor-overlay nil)
+(defvar-local kotlin-mode--anchor-overlay-timer nil)
+
+;;; Indentation struct
+
+(defclass kotlin-mode--indentation ()
+ ((position :initarg :position
+ :type number
+ :accessor kotlin-mode--indentation-position
+ :documentation "the position of the anchor point, such as
+the start of the previous line or the start of the class declaration.")
+ (offset :initarg :offset
+ :type number
+ :initform 0
+ :accessor kotlin-mode--indentation-offset
+ :documentation "the offset from the anchor point. For
+example, when indenting the first line of a class body, its anchor
+point is the start of the class declaration and its offset is
+`kotlin-mode--basic-offset'."))
+ "Indentation.")
+
+;;; Indentation logic
+
+(defun kotlin-mode--indent-line ()
+ "Indent the current line."
+ (let* ((indentation (save-excursion (kotlin-mode--calculate-indent)))
+ (indentation-column
+ (save-excursion
+ (goto-char (kotlin-mode--indentation-position indentation))
+ (+ (current-column) (kotlin-mode--indentation-offset indentation))))
+ (current-indent
+ (save-excursion (back-to-indentation) (current-column))))
+ (if (<= (current-column) current-indent)
+ ;; The point is on the left margin.
+ ;; Move the point to the new leftmost non-comment char.
+ (indent-line-to indentation-column)
+ ;; Keep current relative position from leftmost non-comment char.
+ (save-excursion (indent-line-to indentation-column)))
+ (when kotlin-mode--highlight-anchor
+ (kotlin-mode--highlight-anchor indentation))))
+
+(defun kotlin-mode--calculate-indent ()
+ "Return the indentation of the current line."
+ (back-to-indentation)
+
+ (let ((parser-state (syntax-ppss)))
+ (cond
+ ((nth 4 parser-state)
+ ;; If the 4th element of `(syntax-ppss)' is non-nil, the point is on
+ ;; the 2nd or following lines of a multiline comment, because:
+ ;;
+ ;; - The 4th element of `(syntax-ppss)' is nil on the comment starter.
+ ;; - We have called `back-to-indentation`.
+ (kotlin-mode--calculate-indent-of-multiline-comment))
+
+ ((eq (nth 3 parser-state) t)
+ (kotlin-mode--calculate-indent-of-multiline-string))
+
+ ((looking-at "//")
+ (kotlin-mode--calculate-indent-of-single-line-comment))
+
+ (t
+ (kotlin-mode--calculate-indent-of-code)))))
+
+(defun kotlin-mode--calculate-indent-of-multiline-comment ()
+ "Return the indentation of the current line inside a multiline comment."
+ (back-to-indentation)
+ (let ((comment-beginning-position (nth 8 (syntax-ppss)))
+ (starts-with-asterisk (eq (char-after) ?*)))
+ (forward-line -1)
+ (back-to-indentation)
+ (cond
+ ;; 2nd line of the comment
+ ((<= (point) comment-beginning-position)
+ ;; The point was on the 2nd line of the comment.
+ (goto-char comment-beginning-position)
+ (forward-char)
+ ;; If there are extra characters or spaces after asterisks, align with
+ ;; the first non-space character or end of line. Otherwise, align with
+ ;; the first asterisk.
+ (when (and
+ (looking-at
+ (rx (seq (zero-or-more "*") (one-or-more (not (any "\n*"))))))
+ (not (and kotlin-mode--prepend-asterisk-to-comment-line
+ starts-with-asterisk)))
+ (skip-chars-forward "*")
+ (skip-syntax-forward " "))
+ (make-instance 'kotlin-mode--indentation :position (point) :offset 0))
+
+ ;; The point was on the 3rd or following lines of the comment.
+
+ ;; Before closing delimiter
+ ((= (save-excursion
+ ;; Back to the original line.
+ (forward-line)
+ (back-to-indentation)
+ (point))
+ (save-excursion
+ ;; Goto just before the closing delimiter.
+ (goto-char comment-beginning-position)
+ (if (forward-comment 1)
+ (progn
+ ;; slash
+ (backward-char)
+ (skip-chars-backward "*")
+ (point))
+ -1)))
+ ;; Before the closing delimiter. Align with the first asterisk of the
+ ;; opening delimiter.
+ ;;
+ ;; TODO: If there are multiple asterisks on the closing
+ ;; delimiter, and the middle lines have no leading asterisks,
+ ;; align with the slash of the opening delimiter
+ ;;
+ ;; Example:
+ ;;
+ ;; /*********
+ ;; aaa
+ ;; **********/
+ (goto-char comment-beginning-position)
+ (forward-char)
+ (make-instance 'kotlin-mode--indentation :position (point) :offset 0))
+
+ ;; Otherwise, align with a non-empty preceding line.
+
+ ;; The previous line is empty
+ ((and (bolp) (eolp))
+ ;; Seek a non-empty-line.
+ (while (and (bolp) (eolp) (not (bobp)))
+ (forward-line -1))
+ (forward-line)
+ (kotlin-mode--calculate-indent-of-multiline-comment))
+
+ ;; The previous line is not empty
+ (t
+ ;; Align to this line.
+ (make-instance 'kotlin-mode--indentation :position (point) :offset 0)))))
+
+(defun kotlin-mode--calculate-indent-of-multiline-string ()
+ "Return the indentation of the current line inside a multiline string."
+ (back-to-indentation)
+ (let ((string-beginning-position
+ (save-excursion (kotlin-mode--beginning-of-string))))
+ (if (looking-at "\"\"\"")
+ ;; Indenting the closing delimiter. Align with the start of containing
+ ;; expression with extra indentation.
+ ;;
+ ;; Example:
+ ;;
+ ;; val someString = """
+ ;; Hello.
+ ;; """
+ ;;
+ ;; When aligning to opening delimiter, align without offset:
+ ;; foo("""
+ ;; aaa
+ ;; """)
+ ;;
+ ;; Possible alternative indentation:
+ ;; val someString = """
+ ;; Hello.
+ ;; """
+ (progn
+ (goto-char string-beginning-position)
+ (let ((indentation
+ (kotlin-mode--calculate-indent-of-expression
+ kotlin-mode--multiline-statement-offset)))
+ (if (= (kotlin-mode--indentation-position indentation)
+ string-beginning-position)
+ (make-instance 'kotlin-mode--indentation
+ :position string-beginning-position
+ :offset 0)
+ indentation)))
+ ;; Other than the closing delimiter.
+ (if (and (not (eolp))
+ (not kotlin-mode--indent-nonempty-line-in-multiline-string))
+ ;; The user prefers to keep indentations inside multiline string.
+ (make-instance 'kotlin-mode--indentation :position (point) :offset 0)
+ ;; The user prefers to indent lines inside multiline string.
+ (beginning-of-line)
+ (backward-char)
+ (kotlin-mode--goto-non-template-expression-bol)
+ (back-to-indentation)
+ (cond
+ ;; 2nd line of string
+ ((<= (point) string-beginning-position)
+ ;; The point was on the 2nd line of the string. Align
+ ;; with the start of containing expression with extra
+ ;; indentation.
+ (goto-char string-beginning-position)
+ (kotlin-mode--calculate-indent-of-expression
+ kotlin-mode--multiline-statement-offset))
+
+ ;; The point was on the 3rd or following lines of the string.
+ ;; Align with a non-empty preceding line.
+
+ ;; The previous line is empty
+ ((and (bolp) (eolp))
+ ;; Seek a non-empty-line.
+ (while (and (bolp) (eolp) (not (bobp)))
+ (forward-line -1))
+ (forward-line)
+ (kotlin-mode--calculate-indent-of-multiline-string))
+
+ ;; The previous line is not empty
+ (t
+ ;; Align to this line.
+ (make-instance 'kotlin-mode--indentation
+ :position (point)
+ :offset 0)))))))
+
+(defun kotlin-mode--goto-non-template-expression-bol ()
+ "Back to the beginning of line that is not inside a template expression."
+ (let ((chunk-beginning-position (nth 8 (syntax-ppss)))
+ (matching-bracket t))
+ (while (and matching-bracket
+ (< (line-beginning-position) chunk-beginning-position))
+ (setq matching-bracket
+ (get-text-property
+ chunk-beginning-position 'kotlin-property--matching-bracket))
+ (when matching-bracket
+ (goto-char matching-bracket)
+ (setq chunk-beginning-position (nth 8 (syntax-ppss)))))
+ (beginning-of-line)))
+
+(defun kotlin-mode--calculate-indent-of-single-line-comment ()
+ "Return the indentation of the current line inside a single-line comment."
+ (cond
+ ;; When the comment is beginning of the buffer, indent to the column 0.
+ ((save-excursion
+ (beginning-of-line)
+ (bobp))
+ (make-instance 'kotlin-mode--indentation :position (point-min) :offset 0))
+
+ ;; If the previous line is also a single-line comment, align with it.
+ ((save-excursion
+ (forward-line -1)
+ (skip-syntax-forward " ")
+ (looking-at "//"))
+ (forward-line -1)
+ (skip-syntax-forward " ")
+ (make-instance 'kotlin-mode--indentation :position (point) :offset 0))
+
+ ;; Otherwise, indent like a expression.
+ (t
+ (kotlin-mode--calculate-indent-of-code))))
+
+(defun kotlin-mode--calculate-indent-of-code ()
+ "Return the indentation of the current line outside comments or string."
+ (back-to-indentation)
+ (let* ((previous-token (save-excursion (kotlin-mode--backward-token)))
+ (previous-type (kotlin-mode--token-type previous-token))
+ (previous-text (kotlin-mode--token-text previous-token))
+ (next-token (save-excursion (kotlin-mode--forward-token)))
+ (next-type (kotlin-mode--token-type next-token))
+ (next-text (kotlin-mode--token-text next-token))
+ (next-is-on-current-line
+ (<= (kotlin-mode--token-start next-token) (line-end-position))))
+ (cond
+ ;; Beginning of the buffer
+ ((eq previous-type 'outside-of-buffer)
+ (make-instance 'kotlin-mode--indentation :position (point-min) :offset 0))
+
+ ;; Before } on the current line.
+ ;; Align with the head of the statement.
+ ((and next-is-on-current-line (eq next-type '}))
+ (goto-char (kotlin-mode--token-end next-token))
+ (backward-list)
+ (kotlin-mode--calculate-indent-after-open-curly-brace 0))
+
+ ;; Before ) or ] on the current line.
+ ;; Align with the head of the expression.
+ ((and next-is-on-current-line
+ (memq next-type '(\) \)-before-control-structure-body \])))
+ (goto-char (kotlin-mode--token-end next-token))
+ (backward-list)
+ (kotlin-mode--calculate-indent-of-expression 0))
+
+ ;; Before close angle bracket for generics (>) on the current line.
+ ((and next-is-on-current-line
+ (equal next-text ">")
+ (save-excursion
+ (goto-char (kotlin-mode--token-end next-token))
+ (eq (kotlin-mode--token-type (kotlin-mode--backward-token-or-list))
+ '<>)))
+ (goto-char (kotlin-mode--token-end next-token))
+ (kotlin-mode--backward-token-or-list)
+ (kotlin-mode--calculate-indent-of-expression 0))
+
+ ;; Before end of a template expression on the current line.
+ ((and next-is-on-current-line
+ (eq next-type 'string-chunk-after-template-expression))
+ (goto-char (get-text-property
+ (kotlin-mode--token-start next-token)
+ 'kotlin-property--matching-bracket))
+ ;; Skip "${"
+ (forward-char 2)
+ (kotlin-mode--backward-string-chunk)
+ (kotlin-mode--calculate-indent-after-beginning-of-template-expression
+ 0))
+
+ ;; Before { on the current line
+ ((and next-is-on-current-line (eq next-type '{))
+ (kotlin-mode--calculate-indent-after-open-curly-brace 0))
+
+ ;; Before "else" on the current line
+ ((and next-is-on-current-line (equal next-text "else"))
+ (kotlin-mode--calculate-indent-of-else))
+
+ ;; Before "where" on the current line
+ ((and next-is-on-current-line (equal next-text "where"))
+ (kotlin-mode--find-parent-and-align-with-next
+ kotlin-mode--statement-parent-tokens
+ kotlin-mode--multiline-statement-offset))
+
+ ;; Before "catch" or "finally" on the current line
+ ((and next-is-on-current-line (member next-text '("catch" "finally")))
+ (kotlin-mode--find-parent-and-align-with-next
+ kotlin-mode--statement-parent-tokens
+ 0
+ '()
+ '("catch" "finally")
+ 0))
+
+ ;; Before "=" on the current line
+ ;;
+ ;; fun foo(): T
+ ;; where
+ ;; T: A,
+ ;; T: B
+ ;; = bar()
+ ((and next-is-on-current-line (equal next-text "="))
+ (kotlin-mode--find-parent-and-align-with-next
+ (remove '\, (remove "where" kotlin-mode--expression-parent-tokens))
+ kotlin-mode--multiline-statement-offset))
+
+ ;; Before "," on the current line
+ ((and next-is-on-current-line (eq next-type '\,))
+ (kotlin-mode--calculate-indent-of-prefix-comma))
+
+ ;; After ","
+ ((eq previous-type '\,)
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--calculate-indent-after-comma))
+
+ ;; After "catch". Align with "try".
+ ((equal previous-text "catch")
+ (kotlin-mode--find-parent-and-align-with-next
+ kotlin-mode--statement-parent-tokens
+ kotlin-mode--multiline-statement-offset))
+
+ ;; After {
+ ((eq previous-type '{)
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--calculate-indent-after-open-curly-brace
+ kotlin-mode--basic-offset))
+
+ ;; After ( or [
+ ((memq previous-type '(\( \[))
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--calculate-indent-of-expression
+ kotlin-mode--parenthesized-expression-offset
+ kotlin-mode--parenthesized-expression-offset))
+
+ ;; After open angle bracket for generics (<)
+ ((and (equal previous-text "<")
+ (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (eq (kotlin-mode--token-type (kotlin-mode--forward-token-or-list))
+ '<>)))
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--calculate-indent-of-expression
+ kotlin-mode--parenthesized-expression-offset
+ kotlin-mode--parenthesized-expression-offset))
+
+ ;; After beginning of a template expression
+ ((eq previous-type 'string-chunk-before-template-expression)
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--calculate-indent-after-beginning-of-template-expression
+ kotlin-mode--parenthesized-expression-offset))
+
+ ;; After ; or implicit-\;
+ ((memq previous-type '(\; implicit-\;))
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--calculate-indent-after-semicolon))
+
+ ;; After "->" of lambda parameters
+ ((eq previous-type 'anonymous-function-parameter-arrow)
+ (goto-char (cdr (kotlin-mode--find-containing-brackets
+ (kotlin-mode--token-start previous-token))))
+ (kotlin-mode--calculate-indent-after-open-curly-brace
+ kotlin-mode--basic-offset))
+
+ ;; Before "->" of lambda parameters on the current line
+ ((and next-is-on-current-line
+ (eq next-type 'anonymous-function-parameter-arrow))
+ (if (eq (kotlin-mode--token-type (kotlin-mode--backward-token-or-list))
+ '{)
+ (kotlin-mode--calculate-indent-after-open-curly-brace
+ kotlin-mode--basic-offset)
+ (goto-char (cdr (kotlin-mode--find-containing-brackets
+ (kotlin-mode--token-start next-token))))
+ (kotlin-mode--align-with-next-token (kotlin-mode--forward-token))))
+
+ ;; After "->" of when expression
+ ((eq previous-type 'when-expression-arrow)
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--find-parent-and-align-with-next
+ (cl-remove-if
+ (lambda (e)
+ (memq e '(when-expression-arrow
+ bare-else \(\)-before-control-structure-body)))
+ kotlin-mode--statement-parent-tokens)
+ kotlin-mode--basic-offset))
+
+ ;; Before "->" of when expression on the current line
+ ((and next-is-on-current-line (eq next-type 'when-expression-arrow))
+ (if (equal (kotlin-mode--token-text previous-token) "else")
+ (kotlin-mode--align-with-token
+ previous-token
+ kotlin-mode--basic-offset)
+ (kotlin-mode--find-parent-and-align-with-next
+ (cl-remove-if
+ (lambda (e)
+ (memq e '(when-expression-arrow
+ bare-else \(\)-before-control-structure-body)))
+ kotlin-mode--statement-parent-tokens)
+ kotlin-mode--basic-offset)))
+
+ ;; After "where"
+ ;;
+ ;; class Foo
+ ;; where
+ ;; T: A // align with "where" with offset
+ ;;
+ ;; class Foo where
+ ;; T: A // align with "class" with offset
+ ((equal previous-text "where")
+ (goto-char (kotlin-mode--token-start previous-token))
+ (if (kotlin-mode--bol-other-than-comments-p)
+ (kotlin-mode--align-with-token
+ previous-token
+ kotlin-mode--multiline-statement-offset)
+ (kotlin-mode--find-parent-and-align-with-next
+ kotlin-mode--statement-parent-tokens
+ kotlin-mode--multiline-statement-offset)))
+
+ ;; After if, when, for, or while
+ ((member previous-text '("if" "when" "for" "while"))
+ (kotlin-mode--find-parent-and-align-with-next
+ kotlin-mode--statement-parent-tokens
+ kotlin-mode--multiline-statement-offset))
+
+ ;; After do
+ ((equal previous-text "do")
+ (kotlin-mode--align-with-token
+ previous-token
+ (if (equal next-text "while") 0 kotlin-mode--basic-offset)))
+
+ ;; After else
+ ((equal previous-text "else")
+ (kotlin-mode--align-with-token
+ previous-token
+ kotlin-mode--basic-offset))
+
+ ;; After "if ()", "while ()", or "for ()"
+ ((eq previous-type '\)-before-control-structure-body)
+ (kotlin-mode--backward-token-or-list)
+ (kotlin-mode--find-parent-and-align-with-next
+ kotlin-mode--statement-parent-tokens
+ kotlin-mode--basic-offset
+ '()
+ '("else")
+ kotlin-mode--basic-offset))
+
+ ;; Before ; on the current line
+ ((and next-is-on-current-line (eq next-type '\;))
+ (kotlin-mode--find-parent-and-align-with-next
+ (remove '\; (remove 'implicit-\; kotlin-mode--statement-parent-tokens))
+ 0
+ '(\; implicit-\;)))
+
+ ;; Before "in" on the current line
+ ((and next-is-on-current-line (equal next-text "in"))
+ (kotlin-mode--calculate-indent-before-in))
+
+ ;; After "in"
+ ;;
+ ;; Examples:
+ ;; for (
+ ;; x
+ ;; in
+ ;; xs
+ ;; ) {}
+ ;;
+ ;; for (x
+ ;; in
+ ;; xs) {[]}
+ ;;
+ ;; when (x) {
+ ;; in
+ ;; xs -> y in
+ ;; ys
+ ;; in
+ ;; zs -> 1
+ ;; }
+ ;;
+ ;; Foo<
+ ;; in
+ ;; X,
+ ;; @AAA in
+ ;; X
+ ;; >
+ ;;
+ ;; val x = 1 in
+ ;; array
+ ((equal previous-text "in")
+ (let* ((type-and-parent
+ (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--find-parent-of-in)))
+ (type (car type-and-parent)))
+ (if (or (memq type '(for <>))
+ (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (not (kotlin-mode--bol-other-than-comments-p))))
+ ;; for-statement and type parameters, or "in" is not at
+ ;; the beginning of the line.
+ (progn
+ ;; Indent like a expression
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--calculate-indent-of-expression
+ kotlin-mode--multiline-statement-offset))
+ ;; After "in" at the beginning of the line.
+ ;;
+ ;; Example:
+ ;;
+ ;; when (x) {
+ ;; in
+ ;; xs -> 1
+ ;; in
+ ;; ys -> 1
+ ;; }
+ ;;
+ ;; Pretend a semicolon exists before "in"
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--align-with-next-token
+ (kotlin-mode--backward-token)
+ kotlin-mode--multiline-statement-offset))))
+
+ ;; Before "while" on the current line
+ ((and next-is-on-current-line (equal next-text "while"))
+ (let ((do-token (save-excursion (kotlin-mode--find-do-for-while))))
+ (if do-token
+ (kotlin-mode--align-with-token do-token)
+ (kotlin-mode--calculate-indent-after-semicolon))))
+
+ ;; Inside annotation
+ ((or (and (eq previous-type 'annotation)
+ (or
+ ;; @file
+ ;; : // ← here
+ (eq next-type ':)
+ ;; @file:A
+ ;; .B // ← here
+ ;; @file:A
+ ;; // ← here
+ (member next-text '("." "<"))))
+ ;; @file:
+ ;; [ // ← here
+ (and (eq previous-type ':)
+ (eq next-type '\[)
+ (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (eq (kotlin-mode--token-type (kotlin-mode--backward-token))
+ 'annotation)))
+ ;; @file:
+ ;; A // ← here
+ ;; @file:A
+ ;; .
+ ;; B // ← here
+ ;; @file:A
+ ;; <
+ ;; B
+ ;; > // ← here
+ (and (or (eq next-type 'atom) (equal next-text ">"))
+ (eq (kotlin-mode--token-type
+ (save-excursion
+ (kotlin-mode--extend-annotation-token-backward
+ next-token)))
+ 'annotation)))
+ (let ((start-of-annotation
+ (cond
+ ((eq previous-type 'annotation)
+ (kotlin-mode--token-start previous-token))
+
+ ((eq previous-type ':)
+ (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--token-start (kotlin-mode--backward-token))))
+
+ (t (kotlin-mode--token-start
+ (save-excursion (kotlin-mode--extend-annotation-token-backward
+ next-token))))))
+ (start-of-previous-line
+ (save-excursion
+ (kotlin-mode--backward-token-or-list t)
+ (kotlin-mode--goto-non-comment-bol-with-same-nesting-level t)
+ (point))))
+ (goto-char (max start-of-annotation start-of-previous-line))
+ (kotlin-mode--align-with-current-line
+ (if (< start-of-annotation start-of-previous-line)
+ ;; 3rd or following lines of the annotation.
+ ;; Align with previous line without offset.
+ 0
+ ;; 2nd line of the annotation.
+ ;; Align with the start of the annotation with offset.
+ kotlin-mode--multiline-statement-offset))))
+
+ ;; After annotations or labels
+ ((memq previous-type '(annotation label))
+ (goto-char (kotlin-mode--token-start previous-token))
+ ;; Align with preceding annotation or label at the beginning of
+ ;; a line, or indent like an expression if the annotations are in
+ ;; middle of a line.
+ (let ((token previous-token))
+ (while (and (memq (kotlin-mode--token-type token) '(annotation label))
+ (not (kotlin-mode--bol-other-than-comments-p)))
+ (setq token (kotlin-mode--backward-token)))
+ (unless (memq (kotlin-mode--token-type token) '(annotation label))
+ ;; The annotations are in middle of a line.
+ (goto-char (kotlin-mode--token-end token))))
+ (if (kotlin-mode--bol-other-than-comments-p)
+ ;; The annotations are at the beginning of a line.
+ (kotlin-mode--align-with-current-line)
+ ;; The annotations are in middle of a line.
+ (kotlin-mode--align-with-next-token
+ (kotlin-mode--find-parent-of-expression))))
+
+ ;; Otherwise, it is continuation of the previous line
+ (t
+ (goto-char (kotlin-mode--token-end previous-token))
+ (kotlin-mode--backward-token-or-list)
+ (kotlin-mode--calculate-indent-of-expression
+ kotlin-mode--multiline-statement-offset)))))
+
+(defun kotlin-mode--find-parent-and-align-with-next
+ (parents
+ &optional
+ offset
+ stop-at-eol-token-types
+ stop-at-bol-token-types
+ bol-offset)
+ "Find the parent and return indentation based on it.
+
+A parent is, for example, the open bracket of the containing block or
+semicolon of the preceding statement.
+
+PARENTS is a list of token types that precedes the expression or the statement.
+OFFSET is the offset. If it is omitted, assumed to be 0.
+See `kotlin-mode--backward-sexps-until' for the details of
+STOP-AT-EOL-TOKEN-TYPES and STOP-AT-BOL-TOKEN-TYPES.
+If scanning stops at STOP-AT-EOL-TOKEN-TYPES, align with the next token with
+BOL-OFFSET.
+If scanning stops at STOP-AT-BOL-TOKEN-TYPES, align with that token with
+BOL-OFFSET.
+If STOP-AT-BOL-TOKEN-TYPES or STOP-AT-BOL-TOKEN-TYPES is the symbol
+`any', it matches all tokens.
+The point is assumed to be on the previous line."
+ (save-excursion
+ (let* ((parent (kotlin-mode--backward-sexps-until
+ parents
+ stop-at-eol-token-types
+ stop-at-bol-token-types))
+ (parent-end (kotlin-mode--token-end parent))
+ (stopped-at-parent
+ (or (memq (kotlin-mode--token-type parent) parents)
+ (member (kotlin-mode--token-text parent) parents)
+ (eq (kotlin-mode--token-type parent) 'outside-of-buffer)))
+ (stopped-at-eol
+ (and
+ (not stopped-at-parent)
+ stop-at-eol-token-types
+ (or
+ (eq stop-at-eol-token-types 'any)
+ (memq (kotlin-mode--token-type parent)
+ stop-at-eol-token-types)
+ (member (kotlin-mode--token-text parent)
+ stop-at-eol-token-types)))))
+ (if stopped-at-parent
+ (kotlin-mode--align-with-next-token parent offset)
+ (when stopped-at-eol
+ (goto-char parent-end)
+ (forward-comment (point-max)))
+ (kotlin-mode--align-with-current-line bol-offset)))))
+
+(defun kotlin-mode--calculate-indent-of-expression
+ (&optional
+ offset
+ bol-offset)
+ "Return indentation of the current expression.
+
+the point is assumed to be on the previous line.
+
+OFFSET is the offset. If it is omitted, assumed to be 0.
+If scanning stops at eol, align with the next token with BOL-OFFSET."
+ (save-excursion
+ (let* ((pos (point))
+ (parent-of-previous-line
+ (save-excursion
+ (kotlin-mode--goto-non-comment-bol-with-same-nesting-level)
+ (kotlin-mode--backward-token)))
+ (parent-of-expression (kotlin-mode--find-parent-of-expression)))
+ ;; Skip annotations
+ (goto-char (kotlin-mode--token-end parent-of-expression))
+ (kotlin-mode--forward-annotations)
+ (kotlin-mode--goto-non-comment-bol-with-same-nesting-level)
+ (when (or (< (point) (kotlin-mode--token-end parent-of-expression))
+ (< pos (point)))
+ (goto-char (kotlin-mode--token-end parent-of-expression)))
+ (setq parent-of-expression (kotlin-mode--backward-token))
+ (if (<= (kotlin-mode--token-start parent-of-previous-line)
+ (kotlin-mode--token-start parent-of-expression))
+ ;; let x =
+ ;; 1 + // indenting here
+ ;; 2 +
+ ;; 3
+ ;;
+ ;; Aligns with the parent of the expression with offset.
+ (kotlin-mode--align-with-next-token parent-of-expression offset)
+ ;; let x =
+ ;; 1 +
+ ;; 2 + // indenting here
+ ;; 3 // or here
+ ;;
+ ;; Aligns with the previous line.
+ (kotlin-mode--align-with-next-token parent-of-previous-line
+ bol-offset)))))
+
+(defun kotlin-mode--forward-annotations ()
+ "Skip forward comments, whitespaces, labels, and annotations."
+ (let (token)
+ (while (progn
+ (forward-comment (point-max))
+ (setq token (kotlin-mode--forward-token))
+ (memq (kotlin-mode--token-type token) '(annotation label))))
+ (goto-char (kotlin-mode--token-start token))))
+
+(defun kotlin-mode--backward-annotations ()
+ "Skip backward comments, whitespaces, labels, and annotations."
+ (let (token)
+ (while (progn
+ (forward-comment (- (point)))
+ (setq token (kotlin-mode--backward-token))
+ (memq (kotlin-mode--token-type token) '(annotation label))))
+ (goto-char (kotlin-mode--token-end token))))
+
+(defun kotlin-mode--calculate-indent-after-open-curly-brace (offset)
+ "Return indentation after open curly braces.
+
+Assuming the point is on the open brace.
+OFFSET is the offset of the contents.
+This function is also used for close curly braces."
+ ;; If the statement is multiline expression, aligns with the start of
+ ;; the line on which the open brace is:
+ ;;
+ ;; foo()
+ ;; .then { x ->
+ ;; foo()
+ ;; foo()
+ ;; }
+ ;; .then { x ->
+ ;; foo()
+ ;; foo()
+ ;; }
+ ;;
+ ;; rather than
+ ;;
+ ;; foo()
+ ;; .then { x ->
+ ;; foo()
+ ;; foo()
+ ;; }
+ ;; .then { x ->
+ ;; foo()
+ ;; foo()
+ ;; }
+ ;;
+ ;; Otherwise, aligns with the start of the whole statement:
+ ;;
+ ;; class Foo:
+ ;; Bar {
+ ;; fun foo() {}
+ ;; }
+ ;;
+ ;; rather than
+ ;;
+ ;; class Foo:
+ ;; Bar {
+ ;; fun foo() {}
+ ;; }
+ ;;
+ ;; POSSIBLE IMPROVEMENT: those behavior should be configurable.
+ ;;
+ ;; FIXME: curly braces after binary operator is a part of a
+ ;; multiline expression:
+ ;;
+ ;; class Foo:
+ ;; Bar by xs someInfixOperator { x ->
+ ;; // this is not the body of the class.
+ ;; } {
+ ;; // The body of the class.
+ ;; }
+ (let* ((brace-type-and-keyword-token (kotlin-mode--curly-brace-type))
+ (brace-type (car brace-type-and-keyword-token))
+ (keyword-token (cdr brace-type-and-keyword-token)))
+ (cond
+ ((memq brace-type
+ '(object-literal
+ lambda-literal
+ if-expression
+ try-expression
+ catch-block
+ finally-block
+ when-expression))
+ (goto-char (kotlin-mode--token-start keyword-token))
+ (kotlin-mode--backward-annotations)
+ (unless (eq brace-type 'lambda-literal)
+ (forward-comment (point-max)))
+ (kotlin-mode--calculate-indent-of-expression offset offset))
+
+ ((eq brace-type 'else-block)
+ (goto-char (kotlin-mode--token-start keyword-token))
+ (kotlin-mode--calculate-indent-of-else offset))
+
+ ((eq brace-type 'when-entry)
+ (goto-char (kotlin-mode--token-start keyword-token))
+ (if (equal (kotlin-mode--token-text
+ (save-excursion (kotlin-mode--backward-token)))
+ "else")
+ (kotlin-mode--align-with-token (kotlin-mode--backward-token) offset)
+ (kotlin-mode--find-parent-and-align-with-next
+ (remove 'when-expression-arrow kotlin-mode--statement-parent-tokens)
+ offset)))
+
+ ((memq brace-type '(getter setter))
+ (goto-char (kotlin-mode--token-start keyword-token))
+ (kotlin-mode--try-backward-modifiers)
+ (kotlin-mode--align-with-next-token (kotlin-mode--backward-token) offset))
+
+ (t
+ (goto-char (kotlin-mode--token-start keyword-token))
+ (kotlin-mode--find-parent-and-align-with-next
+ kotlin-mode--statement-parent-tokens
+ offset)))))
+
+(defun kotlin-mode--curly-brace-type ()
+ "Return information about curly brace.
+
+Return a cons (TYPE . KEYWORD-TOKEN) where TYPE is a symbol, and KEYWORD-TOKEN
+is the keyword token:
+- TYPE: class-declaration, KEYWORD-TOKEN: \"class\"
+- TYPE: interface-declaration, KEYWORD-TOKEN: \"interface\"
+- TYPE: object-declaration, KEYWORD-TOKEN: \"object\"
+- TYPE: enum-entry, KEYWORD-TOKEN: identifier
+- TYPE: object-literal, KEYWORD-TOKEN: \"object\"
+- TYPE: anonymous-initializer, KEYWORD-TOKEN: \"init\"
+- TYPE: function-declaration, KEYWORD-TOKEN: \"fun\"
+- TYPE: getter, KEYWORD-TOKEN: \"get\"
+- TYPE: setter, KEYWORD-TOKEN: \"set\"
+- TYPE: secondary-constructor, KEYWORD-TOKEN: \"constructor\"
+- TYPE: for-statement, KEYWORD-TOKEN: \"for\"
+- TYPE: while-statement, KEYWORD-TOKEN: \"while\"
+- TYPE: do-while-statement, KEYWORD-TOKEN: \"do\"
+- TYPE: if-expression, KEYWORD-TOKEN: \"if\"
+- TYPE: else-block, KEYWORD-TOKEN: \"else\"
+- TYPE: when-entry, KEYWORD-TOKEN: \"->\"
+- TYPE: try-expression, KEYWORD-TOKEN: \"try\"
+- TYPE: catch-block, KEYWORD-TOKEN: \"catch\"
+- TYPE: finally-block, KEYWORD-TOKEN: \"finally\"
+- TYPE: lambda-literal, KEYWORD-TOKEN: \"{\"
+- TYPE: when-expression, KEYWORD-TOKEN: \"when\"
+
+Assuming the point is just before the open curly brace."
+ ;; Other braces may appear between the keyword and the open brace:
+ ;;
+ ;; class Foo: Bar by if (aaa) {bbb} else {ccc} {
+ ;; }
+ ;;
+ ;; So we maintain stack of braces and match them with keywords.
+ (save-excursion
+ (let* ((pos (point))
+ (open-brace-points (list pos))
+ (containing-brackets (kotlin-mode--find-containing-brackets (point)))
+ previous-token
+ previous-type
+ previous-text
+ brace-type
+ result)
+ ;; Special handling for enumEntry.
+ (goto-char (cdr containing-brackets))
+ (when (eq (car containing-brackets) '{)
+ (let ((token (kotlin-mode--backward-sexps-until
+ (append kotlin-mode--statement-parent-tokens
+ '("enum")))))
+ (when (equal (kotlin-mode--token-text token) "enum")
+ (goto-char pos)
+ (when (eq (kotlin-mode--token-type
+ (kotlin-mode--backward-sexps-until
+ kotlin-mode--statement-parent-tokens))
+ '{)
+ ;; This must be a enumEntry.
+ (goto-char pos)
+ (setq previous-token (kotlin-mode--backward-token-or-list))
+ (when (eq (kotlin-mode--token-type previous-token) '\(\))
+ (setq previous-token (kotlin-mode--backward-token-or-list)))
+ (setq result (cons 'enum-entry previous-token))))))
+ (unless result
+ ;; Other cases
+ (goto-char pos)
+ (while open-brace-points
+ (setq previous-token (kotlin-mode--backward-token-or-list))
+ (setq previous-type (kotlin-mode--token-type previous-token))
+ (setq previous-text (kotlin-mode--token-text previous-token))
+
+ (cond
+ ;; whenEntry.
+ ;; Stop immediately.
+ ((eq previous-type 'when-expression-arrow)
+ (setq result (cons 'when-entry previous-token))
+ (setq open-brace-points nil))
+
+ ;; Hit beginning of the statement without seeing keyword.
+ ;; Assume it is a lambda expression and stop the loop.
+ ((or (eq previous-type 'outside-of-buffer)
+ (memq previous-type kotlin-mode--statement-parent-tokens)
+ (member previous-text kotlin-mode--statement-parent-tokens))
+ (setq result (cons 'lambda-literal
+ (progn
+ (goto-char pos)
+ (kotlin-mode--forward-token))))
+ (setq open-brace-points nil))
+
+ ;; Found another braces.
+ ;; Push it to the stack and continue the loop.
+ ((eq previous-type '{})
+ (push (point) open-brace-points))
+
+ ;; Simple cases.
+ ;; Those cannot be a part of an expression.
+ ;; Stop immediately.
+ ((member previous-text
+ '("class" "interface" "init" "fun" "get" "set"
+ "for" "while" "do"))
+ (setq brace-type (assoc-default
+ previous-text
+ '(("class" . class-declaration)
+ ("interface" . interface-declaration)
+ ("init" . anonymous-initializer)
+ ;; FIXME handle anonymous function
+ ("fun" . function-declaration)
+ ("get" . getter)
+ ("set" . setter)
+ ("for" . for-statement)
+ ("while" . while-statement)
+ ("do" . do-while-statement))))
+ (setq result (cons brace-type previous-token))
+ (setq open-brace-points nil))
+
+ ;; Semi-simple cases.
+ ;; Those can be a part of an expression and the braces are mandatory.
+ ((member previous-text '("try" "catch" "finally" "when"))
+ (pop open-brace-points)
+ (when (null open-brace-points)
+ (setq brace-type (assoc-default
+ previous-text
+ '(("try" . try-expression)
+ ("catch" . catch-block)
+ ("finally" . finally-block)
+ ("when" . when-expression))))
+ (setq result (cons brace-type previous-token))))
+
+ ;; If and else.
+ ;; Those can be a part of an expression and the braces are optional.
+ ((member previous-text '("if" "else"))
+ (save-excursion
+ (goto-char (kotlin-mode--token-end previous-token))
+ (when (equal previous-text "if")
+ (kotlin-mode--forward-token-or-list))
+
+ (forward-comment (point-max))
+ (when (= (point) (car open-brace-points))
+ (pop open-brace-points)
+ (when (null open-brace-points)
+ (setq brace-type
+ (if (equal previous-text "if")
+ 'if-expression
+ 'else-block))
+ (setq result (cons brace-type previous-token))))))
+
+ ;; Object declaration or object literal.
+ ((equal previous-text "object")
+ (setq brace-type
+ (cond
+ ((save-excursion
+ (equal (kotlin-mode--token-text
+ (kotlin-mode--backward-token))
+ "companion"))
+ 'object-declaration)
+ ((save-excursion
+ (goto-char (kotlin-mode--token-end previous-token))
+ (eq (kotlin-mode--token-type (kotlin-mode--forward-token))
+ 'atom))
+ 'object-declaration)
+ (t 'object-literal)))
+ (pop open-brace-points)
+ (when (or (null open-brace-points)
+ (eq brace-type 'object-declaration))
+ (setq result (cons brace-type previous-token))
+ (setq open-brace-points nil)))
+
+ ;; Primary constructor or secondary constructor.
+ ((equal previous-text "constructor")
+ (let ((parent-token
+ (kotlin-mode--backward-sexps-until
+ (append kotlin-mode--statement-parent-tokens '("class")))))
+ (if (equal (kotlin-mode--token-text parent-token) "class")
+ ;; primary constructor
+ (setq result (cons 'class-declaration parent-token))
+ ;; secondary constructor
+ (setq result (cons 'secondary-constructor previous-token)))
+ (setq open-brace-points nil))))))
+ result)))
+
+(defun kotlin-mode--calculate-indent-of-else (&optional offset)
+ "Return indentation for \"else\" token with OFFSET."
+ ;; Align it with "if" token.
+ ;; Since if-else can be nested, we keep nesting level to find matching "if".
+ (let ((nesting-level 1)
+ previous-token)
+ (while (< 0 nesting-level)
+ (setq previous-token (kotlin-mode--backward-token-or-list))
+ (cond
+ ((equal (kotlin-mode--token-text previous-token) "else")
+ (setq nesting-level (1+ nesting-level)))
+
+ ((equal (kotlin-mode--token-text previous-token) "if")
+ (if (save-excursion
+ ;; "else if" on the same line
+ (and
+ (equal (kotlin-mode--token-text (kotlin-mode--backward-token))
+ "else")
+ (= (progn (kotlin-mode--goto-non-comment-bol)
+ (point))
+ (progn (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--goto-non-comment-bol)
+ (point)))))
+ ;; Skip "else-if" without changing nesting-level.
+ (kotlin-mode--backward-token)
+ (setq nesting-level (1- nesting-level))))
+
+ ((memq (kotlin-mode--token-type previous-token)
+ '({ \( outside-of-buffer))
+ ;; Unmatched if-else
+ (setq nesting-level 0)))))
+ (kotlin-mode--forward-token)
+ (kotlin-mode--calculate-indent-of-expression (or offset 0) (or offset 0)))
+
+(defun kotlin-mode--calculate-indent-of-prefix-comma ()
+ "Return indentation for prefix comma.
+
+Example:
+
+foo( 1
+ , 2
+ , 3
+)
+
+class Foo: A
+ , B
+ , C
+
+class D
+ where A: AAA
+ , B: BBB
+ , C: CCC
+
+This is also known as Utrecht-style in the Haskell community."
+ (let ((parent (kotlin-mode--find-parent-of-list-element t)))
+ (if (eq (kotlin-mode--token-type parent) '\,)
+ ;; The comma was the 2nd or the following commas.
+ ;; Align with the previous comma.
+ (kotlin-mode--align-with-current-line)
+ ;; The comma was the 1st comma.
+ ;; Align with the end of the parent.
+ (goto-char (kotlin-mode--token-end parent))
+ (backward-char)
+ (make-instance 'kotlin-mode--indentation :position (point) :offset 0))))
+
+(defun kotlin-mode--calculate-indent-after-comma ()
+ "Return indentation after comma.
+
+Assuming the point is on the comma."
+ (kotlin-mode--align-with-next-token
+ (kotlin-mode--find-parent-of-list-element nil)))
+
+(defun kotlin-mode--find-parent-of-list-element (&optional utrecht-style)
+ "Move point backward to the parent token of the comma under the point.
+
+If UTRECHT-STYLE is non-nil, stop at a comma at bol. Otherwise, stop at a
+comma at eol."
+ ;; Various examples:
+ ;;
+ ;; val x = foo( // simple case
+ ;; 1,
+ ;; 2,
+ ;; 3
+ ;; )
+ ;;
+ ;; val x = xs[
+ ;; 1,
+ ;; 2,
+ ;; 3
+ ;; ]
+ ;;
+ ;; class Foo: A1(),
+ ;; B1 by object: C1,
+ ;; C2 {
+ ;; },
+ ;; @AAA D1
+ ;; where A: B,
+ ;; A: C,
+ ;; A: D {
+ ;; }
+ ;;
+ ;; when (foo) {
+ ;; object: B1,
+ ;; B2 {
+ ;; },
+ ;; object: B3 by object: B4 {
+ ;; },
+ ;; B5 {
+ ;; } ->
+ ;; 1
+ ;; }
+ ;;
+ ;; fun foo(x: T): Int
+ ;; where
+ ;; T: A,
+ ;; T: B,
+ ;; T: C {
+ ;; }
+ ;;
+ ;; val A.foo: Int
+ ;; where
+ ;; T: A,
+ ;; T: B,
+ ;; T: C
+ ;; get() = aaa
+ ;;
+ ;; enum class Foo(x: Int) {
+ ;; A(1), B(2) {},
+ ;; C(3)
+ ;; }
+ (let ((parent (kotlin-mode--backward-sexps-until
+ (append
+ (remove '\, kotlin-mode--expression-parent-tokens)
+ '("<" : {}))
+ (if utrecht-style nil '(\,))
+ (if utrecht-style '(\,) nil))))
+ (cond
+ ((equal (kotlin-mode--token-text parent) "<")
+ (if (save-excursion
+ (eq (kotlin-mode--token-type (kotlin-mode--forward-token-or-list))
+ '<>))
+ parent
+ (kotlin-mode--find-parent-of-list-element utrecht-style)))
+
+ ((eq (kotlin-mode--token-type parent) '{})
+ (let* ((brace-type-and-keyword-token (kotlin-mode--curly-brace-type))
+ (keyword-token (cdr brace-type-and-keyword-token)))
+ (goto-char (kotlin-mode--token-start keyword-token)))
+ (kotlin-mode--find-parent-of-list-element utrecht-style))
+
+ ((eq (kotlin-mode--token-type parent) ':)
+ (if (kotlin-mode--colon-before-delegation-specifiers-p parent)
+ parent
+ (kotlin-mode--find-parent-of-list-element utrecht-style)))
+
+ (t
+ parent))))
+
+(defun kotlin-mode--find-parent-of-expression ()
+ "Move point backward to the parent token of the expression under the point."
+ ;; TODO Unify with kotlin-mode--find-parent-of-list-element
+ (let ((parent (kotlin-mode--backward-sexps-until
+ (append kotlin-mode--expression-parent-tokens '("<" : {})))))
+ (cond
+ ((equal (kotlin-mode--token-text parent) "<")
+ (if (save-excursion
+ (eq (kotlin-mode--token-type (kotlin-mode--forward-token-or-list))
+ '<>))
+ parent
+ (kotlin-mode--find-parent-of-expression)))
+
+ ((eq (kotlin-mode--token-type parent) '{})
+ (let* ((brace-type-and-keyword-token (kotlin-mode--curly-brace-type))
+ (keyword-token (cdr brace-type-and-keyword-token)))
+ (goto-char (kotlin-mode--token-start keyword-token)))
+ (kotlin-mode--find-parent-of-expression))
+
+ ((eq (kotlin-mode--token-type parent) ':)
+ (if (kotlin-mode--colon-before-delegation-specifiers-p parent)
+ parent
+ (kotlin-mode--find-parent-of-expression)))
+
+ (t
+ parent))))
+
+(defun kotlin-mode--colon-before-delegation-specifiers-p (token)
+ "Return non-nil if TOKEN is a colon before delegationSpecifiers."
+ (and
+ (eq (kotlin-mode--token-type token) ':)
+ (save-excursion
+ (goto-char (kotlin-mode--token-start token))
+ (let (previous-token)
+ ;; Try backward primaryConstructor.
+ (forward-comment (- (point)))
+ (when (eq (char-before) ?\))
+ (kotlin-mode--backward-token-or-list)
+ (setq previous-token (kotlin-mode--backward-token))
+ (if (equal (kotlin-mode--token-text previous-token) "constructor")
+ (kotlin-mode--try-backward-modifiers)
+ (goto-char (kotlin-mode--token-end previous-token))))
+ (kotlin-mode--try-backward-type-parameters)
+ (setq previous-token (kotlin-mode--backward-token-or-list))
+ (or
+ (equal (kotlin-mode--token-text previous-token) "object")
+ (and
+ (eq (kotlin-mode--token-type previous-token) 'atom)
+ (member (kotlin-mode--token-text (kotlin-mode--backward-token-or-list))
+ '("class" "interface" "object"))))))))
+
+(defun kotlin-mode--calculate-indent-after-beginning-of-template-expression
+ (offset)
+ "Return indentation after the beginning of a template expression.
+
+It has offset specified with OFFSET.
+
+Assuming the point is before the string chunk."
+ (let ((pos (point)))
+ (kotlin-mode--forward-string-chunk)
+ (if (< pos (line-beginning-position))
+ ;; The chunk has multiple lines. Align with this line with offset.
+ (progn
+ (back-to-indentation)
+ (make-instance 'kotlin-mode--indentation
+ :position (point)
+ :offset offset))
+ ;; The chunk is single line. Indent like a expression.
+ (goto-char pos)
+ (kotlin-mode--calculate-indent-of-expression offset offset))))
+
+(defun kotlin-mode--calculate-indent-after-semicolon ()
+ "Return indentation after semicolon.
+
+Assuming the point is after the semicolon."
+ (while (save-excursion
+ (memq (kotlin-mode--token-type (kotlin-mode--backward-token))
+ '(\; implicit-\;)))
+ (kotlin-mode--backward-token))
+ (kotlin-mode--find-parent-and-align-with-next
+ (cl-remove-if
+ (lambda (e)
+ (member e '(\; implicit-\; bare-else
+ \(\)-before-control-structure-body when-expression-arrow)))
+ kotlin-mode--statement-parent-tokens)
+ 0
+ '(\; implicit-\;)))
+
+(defun kotlin-mode--calculate-indent-before-in ()
+ "Return indentation after \"in\" token.
+
+Assuming the point is before the token.
+It is also assumed that the point is not just after \"{\" or \"<\"."
+ (let* ((type-and-parent (kotlin-mode--find-parent-of-in))
+ (type (car type-and-parent))
+ (parent (cdr type-and-parent)))
+ (if (memq type '(for when <>))
+ (kotlin-mode--align-with-next-token parent)
+ (kotlin-mode--calculate-indent-after-semicolon))))
+
+(defun kotlin-mode--find-parent-of-in ()
+ "Return parent token of \"in\" token.
+
+Return a cons (TYPE . PARENT) where TYPE is one of symbol `for',
+`when', `<>', or `other' and PARENT is the parent token, one of
+`;', `implicit-;', `(', `{', or \"<\".
+
+Assuming the point is before the token."
+ ;; Examples:
+ ;; for (
+ ;; x
+ ;; in // Align with token after "("
+ ;; xs
+ ;; ) {}
+ ;;
+ ;; for (x
+ ;; in
+ ;; xs) {[]}
+ ;;
+ ;; when (x) {
+ ;; in xs -> 1
+ ;; in ys -> 1
+ ;; }
+ ;;
+ ;; Foo<
+ ;; in X,
+ ;; @AAA
+ ;; in X
+ ;; >
+ ;;
+ ;; val x = 1 // Line breaks are prohibited before infix "in" operator.
+ ;; in array
+ (let ((parent (save-excursion (kotlin-mode--backward-sexps-until
+ '(\; implicit-\; \( { "<")))))
+ (cond
+ ;; Inside "for ()"
+ ((and
+ (eq (kotlin-mode--token-type parent) '\()
+ (save-excursion
+ (goto-char (kotlin-mode--token-start parent))
+ (equal (kotlin-mode--token-text (kotlin-mode--backward-token)) "for")))
+ (cons 'for parent))
+
+ ;; Inside "when () {}"
+ ((and
+ (eq (kotlin-mode--token-type parent) '{)
+ (save-excursion
+ (goto-char (kotlin-mode--token-start parent))
+ (eq (kotlin-mode--token-type (kotlin-mode--backward-token-or-list))
+ '\(\))
+ (equal (kotlin-mode--token-text (kotlin-mode--backward-token))
+ "when")))
+ (cons 'when parent))
+
+ ;; Inside type parameters
+ ((equal (kotlin-mode--token-text parent) "<")
+ (if (save-excursion
+ (goto-char (kotlin-mode--token-start parent))
+ (eq (kotlin-mode--token-type
+ (kotlin-mode--forward-token-or-list))
+ '<>))
+ (cons '<> parent)
+ (save-excursion
+ (goto-char (kotlin-mode--token-start parent))
+ (kotlin-mode--find-parent-of-in))))
+
+ (t
+ (cons 'other parent)))))
+
+(defun kotlin-mode--backward-sexps-until (token-types
+ &optional
+ stop-at-eol-token-types
+ stop-at-bol-token-types)
+ "Backward sexps until a token with one of given token types appears.
+
+Return the token.
+When this function returns, the point is at the start of the token.
+
+TOKEN-TYPES is a list of guard token types. This function backs to a
+token with one of those token types.
+STOP-AT-EOL-TOKEN-TYPES is a list of token types that if we skipped
+the end of a line just after a token with one of given token type, the
+function returns. Typically, this is a list of token types that
+separates list elements (e.g. ',', ';'). If STOP-AT-EOL-TOKEN-TYPES
+is the symbol `any', it matches all tokens.
+STOP-AT-BOL-TOKEN-TYPES is a list of token types that if we hit the
+beginning of a line just before a token with one of given token types,
+the function returns. Typically, this is a list of token types that
+starts list element . If STOP-AT-BOL-TOKEN-TYPES is the symbol `any',
+it matches all tokens."
+ (let*
+ ((parent (kotlin-mode--backward-token-or-list))
+ (type (kotlin-mode--token-type parent))
+ (text (kotlin-mode--token-text parent)))
+ (while (not
+ ;; Stop loop when...
+ (or
+ ;; Hit a guard token.
+ (memq type token-types)
+ (member text token-types)
+
+ ;; Beginning of the buffer.
+ (eq type 'outside-of-buffer)
+
+ ;; When this function is called on "," token before position (1),
+ ;; this function stops just before the "," token after "Foo".
+ ;;
+ ;; Foo,
+ ;; Bar, Baz, // (1)
+ ;; AAA
+ (and stop-at-eol-token-types
+ (or (eq stop-at-eol-token-types 'any)
+ (member type stop-at-eol-token-types)
+ (member text stop-at-eol-token-types))
+ (save-excursion
+ (kotlin-mode--forward-token-or-list)
+ (forward-comment (- (point)))
+ (kotlin-mode--eol-other-than-comments-p)))
+
+ ;; When this function is called on "," token before position
+ ;; (1), this function stops just before ", Bar".
+ ;;
+ ;; , Foo
+ ;; , Bar, Baz:
+ ;; , AAA // (1)
+ (and stop-at-bol-token-types
+ (and
+ (or
+ (eq stop-at-bol-token-types 'any)
+ (member type stop-at-bol-token-types)
+ (member text stop-at-bol-token-types))
+ (kotlin-mode--bol-other-than-comments-p)))))
+ (setq parent (kotlin-mode--backward-token-or-list))
+ (setq type (kotlin-mode--token-type parent))
+ (setq text (kotlin-mode--token-text parent)))
+ parent))
+
+(defun kotlin-mode--align-with-next-token (parent &optional offset)
+ "Return indentation with the next token of PARENT with OFFSET.
+
+Example:
+
+Suppose indenting \"B\":
+
+foo {
+ /* */ A()
+ B()
+}
+
+The parent is \"{\". We align with the comment before \"A\"."
+ (let ((parent-end (kotlin-mode--token-end parent)))
+ (goto-char parent-end)
+ (forward-comment (point-max))
+ (kotlin-mode--goto-non-comment-bol)
+ (when (< (point) parent-end)
+ (goto-char parent-end))
+ (kotlin-mode--skip-whitespaces)
+ (make-instance 'kotlin-mode--indentation
+ :position (point)
+ :offset (or offset 0))))
+
+(defun kotlin-mode--align-with-token (token &optional offset)
+ "Return indentation with the TOKEN with OFFSET.
+
+If the token is preceded by comments on the same line, align with that
+comments instead.
+
+Example:
+
+Suppose indenting \"B\":
+
+foo {
+ /* */ A()
+ B()
+}
+
+We align with the comment before \"A\"."
+ (goto-char (kotlin-mode--token-start token))
+ (forward-comment (- (point)))
+ (kotlin-mode--align-with-next-token (kotlin-mode--backward-token) offset))
+
+(defun kotlin-mode--align-with-current-line (&optional offset)
+ "Return indentation of the current line with OFFSET."
+ (kotlin-mode--goto-non-comment-bol)
+ (kotlin-mode--skip-whitespaces)
+ (make-instance 'kotlin-mode--indentation
+ :position (point)
+ :offset (or offset 0)))
+
+(defun kotlin-mode--goto-non-comment-bol-with-same-nesting-level
+ (&optional use-backward-token-simple)
+ "Back to the beginning of line that is not inside a comment.
+
+See `kotlin-mode--backward-token-or-list' for USE-BACKWARD-TOKEN-SIMPLE."
+ (while (not (kotlin-mode--bol-other-than-comments-p))
+ (kotlin-mode--backward-token-or-list use-backward-token-simple)))
+
+
+;;; indent-new-comment-line
+
+(defun kotlin-mode--indent-new-comment-line (&optional soft)
+ "Break the line at the point and indent the new line.
+
+If the point is inside a comment, continue the comment. If the comment is a
+multiline comment, close the previous comment and start new one if
+`comment-multi-line' is nil.
+See `indent-new-comment-line' for SOFT."
+ (interactive)
+ (let* ((chunk (kotlin-mode--chunk-after))
+ (comment-beginning-position (kotlin-mode--chunk-start chunk)))
+ (if soft (insert-and-inherit ?\n) (newline 1))
+ (delete-horizontal-space)
+
+ ;; Insert a prefix and indent the line.
+ (cond
+ ((not (kotlin-mode--chunk-comment-p chunk))
+ (indent-according-to-mode))
+
+ ((kotlin-mode--chunk-single-line-comment-p chunk)
+ (insert-and-inherit
+ (save-excursion
+ (goto-char comment-beginning-position)
+ (looking-at "/+\\s *")
+ (match-string-no-properties 0)))
+ (indent-according-to-mode))
+
+ ((not comment-multi-line)
+ (insert-and-inherit
+ (save-excursion
+ (goto-char comment-beginning-position)
+ (looking-at "/\\*+\\s *")
+ (match-string-no-properties 0)))
+ ;; Clean up and close the previous line.
+ (save-excursion
+ (beginning-of-line)
+ (backward-char)
+ (delete-horizontal-space)
+ (insert-and-inherit " */"))
+ (indent-according-to-mode))
+
+ (t
+ (kotlin-mode--format-multiline-comment-line-after-newline chunk soft)))
+ ;; Clean up the previous line.
+ (save-excursion
+ (beginning-of-line)
+ (backward-char)
+ (delete-horizontal-space))))
+
+(defun kotlin-mode--format-multiline-comment-line-after-newline (chunk soft)
+ "Insert prefix and indent current line in multiline comment.
+
+The point is assumed inside multiline comment and just after newline.
+
+The closing delimiter is also inserted and/or formatted depending on custom
+variables `kotlin-mode--auto-close-multiline-comment' and
+`kotlin-mode--break-line-before-comment-close'.
+
+CHUNK is the comment chunk.
+
+See `indent-new-comment-line' for SOFT."
+ (let ((comment-beginning-position (kotlin-mode--chunk-start chunk)))
+ (cond
+ ((save-excursion
+ (forward-line -1)
+ (<= (point) comment-beginning-position))
+ ;; The point was on the 2nd line of the comment.
+
+ ;; If the comment have only one line, delete a space after asterisk.
+ ;;
+ ;; Example:
+ ;; /** aaa */
+ ;; ↓
+ ;; /**
+ ;; * aaa
+ ;; */
+ ;;
+ ;; /** aaa */
+ ;; ↓
+ ;; /**
+ ;; * aaa
+ ;; */
+ ;;
+ ;;
+ ;; If the comment spans several lines, keep spaces.
+ ;;
+ ;; /** aaa
+ ;; */
+ ;; ↓
+ ;; /**
+ ;; * aaa
+ ;; */
+ (when (= (line-beginning-position)
+ (save-excursion
+ (goto-char comment-beginning-position)
+ (forward-comment 1)
+ (line-beginning-position)))
+ (save-excursion
+ (goto-char comment-beginning-position)
+ (forward-char)
+ (skip-chars-forward "*")
+ (when (looking-at " [ \t]*$")
+ (delete-char 1))))
+
+ ;; If the point is just before the closing delimiter, break the line.
+ (when (and kotlin-mode--break-line-before-comment-close
+ (= (point)
+ (save-excursion
+ (goto-char comment-beginning-position)
+ (if (forward-comment 1)
+ (progn
+ (backward-char)
+ (skip-chars-backward "*")
+ (point))
+ -1))))
+ (save-excursion
+ (if soft (insert-and-inherit ?\n) (newline 1))
+ (indent-according-to-mode)))
+
+ ;; Invoke `kotlin-mode--indent-line`.
+ (indent-according-to-mode)
+
+ ;; Insert or replace a space to an asterisk.
+ (when kotlin-mode--prepend-asterisk-to-comment-line
+ (let ((columns-from-end (- (line-end-position) (point))))
+ (move-to-column
+ (save-excursion
+ (goto-char comment-beginning-position)
+ (forward-char)
+ (current-column)))
+ (insert-and-inherit "*")
+ (when (eq (char-after) ?\s)
+ (delete-char 1))
+ (when (and
+ kotlin-mode--insert-space-after-asterisk-in-comment
+ (not (eq (char-after) ?\s)))
+ (insert-and-inherit " "))
+ (goto-char (- (line-end-position) columns-from-end)))))
+
+ ;; The point was on the 3nd or following lines of
+ ;; the comment.
+ ;; Use the prefix of the previous line.
+
+ ((and
+ kotlin-mode--prepend-asterisk-to-comment-line
+ (save-excursion
+ (forward-line -1)
+ (looking-at "\\s *\\(\\*+\\s *\\)")))
+ ;; The previous line has a prefix. Use it.
+ (insert-and-inherit (match-string-no-properties 1))
+ (indent-according-to-mode))
+
+ (t
+ ;; Use the default indentation.
+ (indent-according-to-mode)))
+
+ ;; Close incomplete multiline comment.
+ (when (and kotlin-mode--auto-close-multiline-comment
+ (kotlin-mode--incomplete-comment-p chunk))
+ (save-excursion
+ (end-of-line)
+ (when comment-multi-line
+ (if soft (insert-and-inherit ?\n) (newline 1)))
+ (insert-and-inherit "*/")
+ (indent-according-to-mode)))
+
+ ;; Make sure the closing delimiter is on its own line.
+ (when kotlin-mode--break-line-before-comment-close
+ (save-excursion
+ (goto-char comment-beginning-position)
+ (when (forward-comment 1)
+ (backward-char)
+ (skip-chars-backward "*")
+ (skip-syntax-backward " ")
+ (when (not (bolp))
+ (if soft (insert-and-inherit ?\n) (newline 1))
+ (indent-according-to-mode)))))))
+
+(defun kotlin-mode--post-self-insert ()
+ "Miscellaneous logic for electric indentation."
+ (cond
+ ;; Indent electrically and insert a space when "*" is inserted at the
+ ;; beginning of a line inside a multiline comment.
+ ((and
+ kotlin-mode--prepend-asterisk-to-comment-line
+ (eq last-command-event ?*)
+ (kotlin-mode--chunk-comment-p (kotlin-mode--chunk-after))
+ (save-excursion (backward-char) (skip-syntax-backward " ") (bolp)))
+ (when kotlin-mode--insert-space-after-asterisk-in-comment
+ (insert-and-inherit " "))
+ (when electric-indent-mode
+ (indent-according-to-mode)))
+
+ ;; Fixe "* /" at the end of a multiline comment to "*/".
+ ((and
+ kotlin-mode--fix-comment-close
+ (eq last-command-event ?/)
+ (let ((chunk (kotlin-mode--chunk-after))
+ (pos (point)))
+ (and
+ (kotlin-mode--chunk-comment-p chunk)
+ (save-excursion
+ (beginning-of-line)
+ (and
+ (looking-at "^\\s *\\*\\s +/")
+ (eq (match-end 0) pos)
+ (kotlin-mode--incomplete-comment-p chunk))))))
+ (backward-char)
+ (delete-horizontal-space)
+ (forward-char))
+
+ ;; Indent electrically when "}" is inserted at bol as the end of a string
+ ;; interpolation.
+ ((and
+ electric-indent-mode
+ (eq last-command-event ?\})
+ (save-excursion (backward-char) (skip-syntax-backward " ") (bolp))
+ (eq (kotlin-mode--chunk-start (kotlin-mode--chunk-after)) (1- (point))))
+ (indent-according-to-mode))
+
+ ;; Indent electrically after newline inside strings and comments.
+ ;; Unlike `electric-indent-mode', the previous line is not indented.
+ ((and
+ electric-indent-mode
+ (eq last-command-event ?\n))
+ (let ((chunk (kotlin-mode--chunk-after)))
+ (if (kotlin-mode--chunk-multiline-comment-p chunk)
+ (progn
+ (delete-horizontal-space)
+ (kotlin-mode--format-multiline-comment-line-after-newline
+ chunk
+ (not use-hard-newlines)))
+ (indent-according-to-mode)))
+ (save-excursion
+ (beginning-of-line)
+ (backward-char)
+ (delete-horizontal-space)))))
+
+(defun kotlin-mode--highlight-anchor (indentation)
+ "Highlight the anchor point of the INDENTATION."
+ (move-overlay
+ kotlin-mode--anchor-overlay
+ (kotlin-mode--indentation-position indentation)
+ (1+ (kotlin-mode--indentation-position indentation)))
+
+ (overlay-put kotlin-mode--anchor-overlay 'face 'highlight)
+
+ (when kotlin-mode--anchor-overlay-timer
+ (cancel-timer kotlin-mode--anchor-overlay-timer))
+
+ (let ((buffer (current-buffer)))
+ (setq kotlin-mode--anchor-overlay-timer
+ (run-at-time
+ "1 sec"
+ nil
+ (lambda ()
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (delete-overlay kotlin-mode--anchor-overlay)
+ (setq kotlin-mode--anchor-overlay-timer nil))))))))
+
+(provide 'kotlin-mode-indent)
+
+;;; kotlin-mode-indent.el ends here
diff --git a/kotlin-mode-lexer.el b/kotlin-mode-lexer.el
index 29576be..c19e999 100644
--- a/kotlin-mode-lexer.el
+++ b/kotlin-mode-lexer.el
@@ -1,8 +1,10 @@
;;; kotlin-mode-lexer.el --- Major mode for kotlin, lexer -*- lexical-binding: t; -*-
+;; Copyright © 2015 Shodai Yokoyama
;; Copyright © 2019 taku0
-;; Author: taku0 (http://github.com/taku0)
+;; Author: Shodai Yokoyama (quantumcars@gmail.com)
+;; taku0 (http://github.com/taku0)
;; Keywords: languages
;; Package-Requires: ((emacs "24.3"))
@@ -51,6 +53,41 @@
;;
;; This is not a official term; used only in kotlin-mode.
+;;; Brackets
+
+(defvar-local kotlin-mode--bracket-positions '()
+ "List of position of brackets.
+
+Element of the list is a cons (TYPE . POSITION) where TYPE is one of
+\(, ), {, }, [, or ], and POSITION is the position of the token.
+
+Elements are sorted by the position in descending order.")
+
+(defun kotlin-mode--clear-bracket-positions-after (position)
+ "Remove bracket positions after or equal to POSITION from cache."
+ (while (and kotlin-mode--bracket-positions
+ (<= position (cdar kotlin-mode--bracket-positions)))
+ (pop kotlin-mode--bracket-positions)))
+
+(defun kotlin-mode--find-containing-brackets (position)
+ "Return start position of innermost brackets containing POSITION.
+
+Return a cons (TYPE . POSITION) where TYPE is one of (, ), {, }, [, ],
+or outside-of-buffer, and POSITION is the position of the token."
+ (let ((brackets kotlin-mode--bracket-positions)
+ (nesting-level 1))
+ (while (and brackets (<= position (cdar brackets)))
+ (pop brackets))
+ (while (and brackets (not (zerop nesting-level)))
+ (if (memq (caar brackets) '(\( \[ {))
+ (setq nesting-level (1- nesting-level))
+ (setq nesting-level (1+ nesting-level)))
+ (unless (zerop nesting-level)
+ (pop brackets)))
+ (if brackets
+ (car brackets)
+ (cons 'outside-of-buffer (point-min)))))
+
;;; Text properties
;; See also doc/string_properties.png.
@@ -93,7 +130,9 @@ Mark the beginning of and the end of single-line/multiline
strings, character literals, backquoted identifiers between the
position START and END as general string delimiters.
-Intended for `syntax-propertize-function'."
+Intended for `syntax-propertize-function'.
+
+Also track position of brackets in `kotlin-mode--bracket-positions'."
(remove-text-properties start end
'(syntax-table
nil
@@ -103,6 +142,7 @@ Intended for `syntax-propertize-function'."
nil
kotlin-property--interpolation
nil))
+ (kotlin-mode--clear-bracket-positions-after start)
(let* ((chunk (kotlin-mode--chunk-after (syntax-ppss start))))
(cond
((kotlin-mode--chunk-multiline-string-p chunk)
@@ -130,50 +170,70 @@ Mark the beginning of and the end of single-line/multiline
strings and character literals between the current position and
END as general string delimiters.
-Assuming the cursor is not on strings, character-literal,
+Assuming the point is not on strings, character-literal,
backquoted identifier, nor comments.
If NESTING-LEVEL is non-zero, nesting of brackets are tracked and
the scan stops where the level becomes zero."
(let ((found-matching-bracket nil)
- (pattern (rx (or "\"\"\"" "\"" "//" "/*" "{" "}" "'" "`"))))
+ (pattern
+ (rx (or "\"\"\"" "\"" "//" "/*" "{" "}" "(" ")" "[" "]" "'" "`")))
+ match-string
+ start)
(while (and (not found-matching-bracket)
(< (point) end)
(search-forward-regexp pattern end t))
+ (setq match-string (match-string-no-properties 0))
+ (setq start (match-beginning 0))
(cond
- ((member (match-string-no-properties 0) '("\"\"\"" "\"" "'" "`"))
- (let ((start (match-beginning 0))
- (quotation (match-string-no-properties 0)))
- (put-text-property start (1+ start)
+ ;; Quotes
+ ((member match-string '("\"\"\"" "\"" "'" "`"))
+ ;; Mark the start of the quotes as a generic string delimiter.
+ (put-text-property start (1+ start)
+ 'syntax-table
+ (string-to-syntax "|"))
+ ;; Scan until end of the string.
+ (kotlin-mode--syntax-propertize-end-of-string end match-string)
+ ;; Mark whole string, including template expressions,
+ ;; with `syntax-multiline'. The property is used by
+ ;; `kotlin-mode--syntax-propertize-extend-region'.
+ (put-text-property start (point) 'syntax-multiline t)
+
+ (when (equal match-string "`")
+ ;; Backquotes cannot be escaped. So declare the backslashes in
+ ;; the identifier are normal characters, not escape-syntax characters.
+ (put-text-property (1+ start) (1- (point))
'syntax-table
- (string-to-syntax "|"))
- (kotlin-mode--syntax-propertize-end-of-string end quotation)
- (put-text-property start (point) 'syntax-multiline t)
-
- (when (equal quotation "`")
- ;; Backquotes cannot be escaped. So declares the backslashes in
- ;; the identifier are not a escape-syntax characters.
- (put-text-property (1+ start) (1- (point))
- 'syntax-table
- (string-to-syntax "w")))))
+ (string-to-syntax "w"))))
- ((equal "//" (match-string-no-properties 0))
- (goto-char (match-beginning 0))
+ ;; Single-line comment
+ ((equal "//" match-string)
+ (goto-char start)
(forward-comment (point-max)))
- ((equal "/*" (match-string-no-properties 0))
- (goto-char (match-beginning 0))
+ ;; Multiline comment
+ ((equal "/*" match-string)
+ (goto-char start)
(forward-comment (point-max)))
- ((and (equal "{" (match-string-no-properties 0))
+ ;; Open curly bracket
+ ((and (equal "{" match-string)
(/= nesting-level 0))
+ (push (cons (intern match-string) start) kotlin-mode--bracket-positions)
(setq nesting-level (1+ nesting-level)))
- ((and (equal "}" (match-string-no-properties 0))
+ ;; Close curly bracket
+ ((and (equal "}" match-string)
(/= nesting-level 0))
+ (push (cons (intern match-string) start) kotlin-mode--bracket-positions)
(setq nesting-level (1- nesting-level))
(when (= nesting-level 0)
- (setq found-matching-bracket t)))))
+ (setq found-matching-bracket t)))
+
+ ;; Other brackets
+ ((member match-string '("(" ")" "[" "]" "{" "}"))
+ (push (cons (intern match-string) start)
+ kotlin-mode--bracket-positions))))
(unless found-matching-bracket
(goto-char end))
found-matching-bracket))
@@ -181,7 +241,7 @@ the scan stops where the level becomes zero."
(defun kotlin-mode--syntax-propertize-end-of-string (end quotation)
"Move point to the end of single-line/multiline string.
-Assuming the cursor is on a string, a character literal, or a backquoted
+Assuming the point is on a string, a character literal, or a backquoted
identifier.
If the string go beyond END, stop there.
The string should be terminated with QUOTATION."
@@ -195,12 +255,15 @@ The string should be terminated with QUOTATION."
(and "`" (+ (not (any "`\n"))) "`")))))
end t))
(cond
+ ;; End of the string
((and (equal quotation (match-string-no-properties 0))
(or (equal quotation "`") ; backquotes cannot be escaped
(not (kotlin-mode--escaped-p (match-beginning 0)))))
(put-text-property (1- (point)) (point)
'syntax-table
(string-to-syntax "|")))
+
+ ;; Start of a template expression
((and (equal "${" (match-string-no-properties 0))
(member quotation '("\"\"\"" "\""))
;; Dollar signs cannot be escaped, so we don't need to check it.
@@ -214,30 +277,39 @@ The string should be terminated with QUOTATION."
(backward-char) ;; {
(backward-char) ;; $
(point))))
- ;; Declares the open bracket is a generic string delimiter.
+ ;; Keep the position of the open bracket.
+ (push (cons '{ (1+ start)) kotlin-mode--bracket-positions)
+ ;; Declare the open bracket is a generic string delimiter.
(put-text-property
(1- pos-after-open-bracket) pos-after-open-bracket
'syntax-table
(string-to-syntax "|"))
+ ;; Try to skip
(when (kotlin-mode--syntax-propertize-scan end 1)
;; Found the matching bracket. Going further.
- ;; Declares the close bracket is a generic string delimiter.
+ ;; Declare the close bracket is a generic string delimiter.
(put-text-property (1- (point)) (point)
'syntax-table
(string-to-syntax "|"))
- ;; Records the positions.
+ ;; Record the positions.
(put-text-property (1- (point)) (point)
'kotlin-property--matching-bracket
start)
(put-text-property start pos-after-open-bracket
'kotlin-property--matching-bracket
(1- (point)))
+ ;; Proceed.
(kotlin-mode--syntax-propertize-end-of-string end quotation))))
- ((match-string-no-properties 0)
+
+ ;; Template expression without braces
+ ((and (eq (aref (match-string-no-properties 0) 0) ?$)
+ (not (eq (aref (match-string-no-properties 0) 1) ?{)))
(put-text-property (match-beginning 0) (1+ (match-beginning 0))
'kotlin-property--interpolation
(match-data))
(kotlin-mode--syntax-propertize-end-of-string end quotation))
+
+ ;; Others
(t
(kotlin-mode--syntax-propertize-end-of-string end quotation)))
(goto-char end)))
@@ -260,7 +332,6 @@ Return nil otherwise."
(defclass kotlin-mode--chunk ()
((type :initarg :type
:type symbol
- :accessor kotlin-mode--chunk-type
:documentation "The type of the chunk.
Valid values:
@@ -272,12 +343,19 @@ Valid values:
- backquoted-identifier")
(start :initarg :start
:type number
- :accessor kotlin-mode--chunk-start
:documentation "The start position of the chunk."))
"String-chunks, comments, character literals, or backquoted identifiers.
It have the type and the start position.")
+(defun kotlin-mode--chunk-type (chunk)
+ "Return the type of the CHUNK."
+ (and chunk (oref chunk type)))
+
+(defun kotlin-mode--chunk-start (chunk)
+ "Return the start position of the CHUNK."
+ (and chunk (oref chunk start)))
+
(defun kotlin-mode--chunk-comment-p (chunk)
"Return non-nil if the CHUNK is a comment."
(and chunk
@@ -314,10 +392,19 @@ It have the type and the start position.")
"Return non-nil if the CHUNK is a backquoted identifier."
(and chunk (eq (kotlin-mode--chunk-type chunk) 'backquoted-identifier)))
+(defun kotlin-mode--incomplete-comment-p (chunk)
+ "Return t if the CHUNK is incomplete comment.
+
+Return nil otherwise."
+ (and (kotlin-mode--chunk-comment-p chunk)
+ (save-excursion
+ (goto-char (kotlin-mode--chunk-start chunk))
+ (not (forward-comment 1)))))
+
(defun kotlin-mode--chunk-after (&optional parser-state)
- "Return the chunk at the cursor.
+ "Return the chunk at the point.
-If the cursor is outside of strings and comments, return nil.
+If the point is outside of strings and comments, return nil.
If PARSER-STATE is given, it is used instead of (syntax-ppss)."
(save-excursion
@@ -330,14 +417,27 @@ If PARSER-STATE is given, it is used instead of (syntax-ppss)."
;; Syntax category "|" is attached to both single-line and multiline
;; string delimiters. So (nth 3 parser-state) may be t even for
;; single-line string delimiters.
- (if (save-excursion (goto-char (nth 8 parser-state))
- (looking-at "\"\"\""))
+ (save-excursion
+ (goto-char (nth 8 parser-state))
+ (forward-char)
+ (kotlin-mode--beginning-of-string)
+ (cond
+ ((looking-at "\"\"\"")
(make-instance 'kotlin-mode--chunk
:type 'multiline-string
- :start (nth 8 parser-state))
- (make-instance 'kotlin-mode--chunk
- :type 'single-line-string
- :start (nth 8 parser-state))))
+ :start (nth 8 parser-state)))
+ ((looking-at "`")
+ (make-instance 'kotlin-mode--chunk
+ :type 'backquoted-identifier
+ :start (nth 8 parser-state)))
+ ((looking-at "'")
+ (make-instance 'kotlin-mode--chunk
+ :type 'character-literal
+ :start (nth 8 parser-state)))
+ (t
+ (make-instance 'kotlin-mode--chunk
+ :type 'single-line-string
+ :start (nth 8 parser-state))))))
((eq (nth 4 parser-state) t)
(make-instance 'kotlin-mode--chunk
:type 'single-line-comment
@@ -357,6 +457,2135 @@ If PARSER-STATE is given, it is used instead of (syntax-ppss)."
(t
nil))))
+;; Syntax table
+
+(defvar kotlin-mode-syntax-table
+ (let ((st (make-syntax-table)))
+
+ ;; Strings
+ (modify-syntax-entry ?\" "\"" st)
+ (modify-syntax-entry ?\' "\"" st)
+ (modify-syntax-entry ?` "\"" st)
+
+ ;; `_' and `@' as being a valid part of a symbol
+ (modify-syntax-entry ?_ "_" st)
+ (modify-syntax-entry ?@ "_" st)
+
+ ;; b-style comment
+ (modify-syntax-entry ?/ ". 124b" st)
+ (modify-syntax-entry ?* ". 23n" st)
+ (modify-syntax-entry ?\n "> b" st)
+ (modify-syntax-entry ?\r "> b" st)
+ st))
+
+;; Token
+
+(defclass kotlin-mode--token ()
+ ((type :initarg :type
+ :type symbol
+ :documentation "The type of the token.
+
+Token types is one of the following symbols:
+
+- operator (including as, as?, is, !is, in, !in, ., and ->)
+- annotation
+ (e.g. @ABC, @ABC(def), @file:ABC, @[ABC DEF(ghi)], or @file:[ABC DEF(ghi)])
+- atom (including keywords, numbers, strings, and unknown tokens)
+- label
+- return (including return@identifier)
+- continue (including continue@identifier)
+- beak (including beak@identifier)
+- this (including this@identifier)
+- super (including super@identifier)
+- [
+- ]
+- {
+- }
+- (
+- )
+- )-before-control-structure-body
+- ,
+- ;
+- implicit-;
+- < (as an angle bracket)
+- > (as an angle bracket)
+- :
+- string-chunk-after-template-expression (part of a string ending with \"}\")
+- string-chunk-before-template-expression (part of a string ending with \"${\")
+- bare-else (\"else\" not followed by \"if\" on the same line, \"->\", or \"{\")
+- outside-of-buffer
+- anonymous-function-parameter-arrow
+- when-expression-arrow
+
+Additionally, `kotlin-mode--backward-token-or-list' may return a parenthesized
+expression as a token with one of the following types:
+- ()
+- ()-before-control-structure-body
+- []
+- {}
+- <>" )
+ (text :initarg :text
+ :type string
+ :documentation "The text of the token.")
+ (start :initarg :start
+ :type integer
+ :documentation "The start position of the token.")
+ (end :initarg :end
+ :type integer
+ :documentation "The point after the token."))
+ "Token of Kotlin.")
+
+(defun kotlin-mode--token-type (token)
+ "Return the type of TOKEN."
+ (and token (oref token type)))
+
+(defun kotlin-mode--token-text (token)
+ "Return the text of TOKEN."
+ (and token (oref token text)))
+
+(defun kotlin-mode--token-start (token)
+ "Return the start position of TOKEN."
+ (and token (oref token start)))
+
+(defun kotlin-mode--token-end (token)
+ "Return the end position of TOKEN."
+ (and token (oref token end)))
+
+;; Token level movements and predicates.
+
+(defconst kotlin-mode--inheritance-modifier-keywords
+ '("open" "final" "abstract"))
+
+(defconst kotlin-mode--visibility-modifier-keywords
+ '("public" "private" "internal" "protected"))
+
+(defconst kotlin-mode--member-modifier-keywords
+ '("lateinit" "override"))
+
+(defconst kotlin-mode--companion-modifier-keywords
+ '("companion"))
+
+(defconst kotlin-mode--class-modifier-keywords
+ '("enum" "sealed" "annotation" "data" "inner"))
+
+(defconst kotlin-mode--property-modifier-keywords
+ '("const"))
+
+(defconst kotlin-mode--platform-modifier-keywords
+ '("expect" "actual"))
+
+(defconst kotlin-mode--parameter-modifier-keywords
+ '("vararg" "crossinline" "noinline"))
+
+(defconst kotlin-mode--function-modifier-keywords
+ '("tailrec" "operator" "infix" "inline" "value" "external" "suspend"))
+
+(defconst kotlin-mode--modifier-keywords
+ (append kotlin-mode--inheritance-modifier-keywords
+ kotlin-mode--visibility-modifier-keywords
+ kotlin-mode--member-modifier-keywords
+ kotlin-mode--class-modifier-keywords
+ kotlin-mode--property-modifier-keywords
+ kotlin-mode--platform-modifier-keywords
+ kotlin-mode--parameter-modifier-keywords
+ kotlin-mode--function-modifier-keywords))
+
+(defun kotlin-mode--implicit-semi-p ()
+ "Return non-nil if the point is after the end of a statement."
+ (let ((previous-token (save-excursion
+ (kotlin-mode--extend-annotation-token-backward
+ (kotlin-mode--backward-token-simple))))
+ (next-token (save-excursion
+ (kotlin-mode--extend-annotation-token-forward
+ (kotlin-mode--forward-token-simple)))))
+ ;; If the point is on the empty line, pretend an identifier is on the line.
+ (when (and
+ (< (kotlin-mode--token-end previous-token) (line-beginning-position))
+ (< (line-end-position) (kotlin-mode--token-start next-token)))
+ (setq next-token (make-instance 'kotlin-mode--token
+ :type 'atom
+ :text ""
+ :start (point)
+ :end (point))))
+ (cond
+ ;; Tokens that end a statement
+ ((memq (kotlin-mode--token-text previous-token)
+ '("return" "continue" "break"))
+ t)
+
+ ;; .* in import declarations end a statement.
+ ((and (equal (kotlin-mode--token-text previous-token) "*")
+ (equal (kotlin-mode--token-text
+ (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--backward-token-simple)))
+ "."))
+ t)
+
+ ;; Suppress implicit semicolon after "if ()", "while ()", "for ()"
+ ((kotlin-mode--close-parenthesis-before-control-structure-body-p
+ previous-token)
+ nil)
+
+ ;; Annotations and modifiers, that cannot end a statement/declaration
+ ((or
+ ;; Annotations
+ (and
+ (eq (kotlin-mode--token-type previous-token) 'annotation)
+ ;; Exclude super@label. Note that this@label is handled by
+ ;; `kotlin-mode--backward-token-simple'
+ (not (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (and (eq (char-before) ?>)
+ (kotlin-mode--try-backward-type-parameters)
+ (equal (kotlin-mode--token-text
+ (kotlin-mode--backward-token-simple))
+ "super")))))
+ ;; Labels
+ (eq (kotlin-mode--token-type previous-token) 'label)
+ ;; Modifiers
+ (and (member (kotlin-mode--token-text previous-token)
+ kotlin-mode--modifier-keywords)
+ (not (equal (kotlin-mode--token-text previous-token) "value"))))
+ nil)
+
+ ;; Tokens that cannot end a statement
+ ;;
+ ;; TODO prefix ++ and --
+ ;; TODO infix function call
+ ;; TODO prefix !, especially !!
+ ;;
+ ;; Example:
+ ;; var shl = 1
+ ;; val x = shl shl shl
+ ;; shl < 100 && foo() // this is not a continuation of the previous line.
+ ;;
+ ;; var shl = 1
+ ;; val x = shl shl
+ ;; shl < 100 && foo() // this is a continuation of the previous line.
+ ;;
+ ;; var shl = 1
+ ;; val x = shl shl shl ++
+ ;; shl < 100 && foo() // this is not a continuation of the previous line.
+ ;;
+ ;; var shl = 1
+ ;; val x = shl shl ++
+ ;; shl < 100 && foo() // this is a continuation of the previous line.
+ ;;
+ ;; val x = foo()!!
+ ;; foo() // this is not a continuation of the previous line.
+ ;;
+ ;; val x = !!
+ ;; foo() // this is a continuation of the previous line.
+ ((or
+ (memq
+ (kotlin-mode--token-type previous-token)
+ '(implicit-\; string-chunk-before-template-expression))
+ (member
+ (kotlin-mode--token-text previous-token)
+ '("(" "{" "[" "*" "%" "/" "+" "-" "&&" "||" ":" "&"
+ "=" "+=" "-=" "*=" "/=" "%="
+ "->" "." ".." "..<" "::" "?:" "?." "<=" ">=" "!=" "!==" "==" "==="
+ "as" "as?" "is" "!is" "in" "!in" "," ";" "{" "[" "("
+ ;; "class" will be handled later.
+ "package" "import" "interface" "fun" "object"
+ "val" "var" "typealias" "constructor" "by" "companion" "init"
+ "where" "if" "else" "when" "try" "catch" "finally" "for" "do"
+ "while" "throw" "out" "reified"))
+ ;; Inequality operator cannot end a statement/declaration.
+ (and (eq (kotlin-mode--token-type previous-token) 'operator)
+ (member (kotlin-mode--token-text previous-token) '("<" ">"))))
+ nil)
+
+ ;; "class" cannot end a statement unless preceded by "::".
+ ;;
+ ;; Example:
+ ;;
+ ;; class // No implicit semicolon here
+ ;; Foo {
+ ;; }
+ ;;
+ ;; val x = Foo ::
+ ;; class // Implicit semicolon here
+ ;; foo()
+ ((and (equal (kotlin-mode--token-text previous-token) "class")
+ (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (not (equal (kotlin-mode--token-text
+ (kotlin-mode--backward-token-simple))
+ "::"))))
+ nil)
+
+ ;; Annotations, labels, and modifiers, that start a statement/declaration,
+ ;; except for getter and setter.
+ ;;
+ ;; "suspend" is excluded because it can be used as type modifier.
+ ((or
+ (memq (kotlin-mode--token-type next-token) '(annotation label))
+ (member (kotlin-mode--token-text next-token)
+ (remove "suspend" kotlin-mode--modifier-keywords)))
+ (not (save-excursion
+ (kotlin-mode--try-forward-modifiers)
+ (member (kotlin-mode--token-text
+ (kotlin-mode--forward-token-simple))
+ '("get" "set")))))
+
+ ;; Tokens that start a statement/declaration
+ ((member
+ (kotlin-mode--token-text next-token)
+ ;; "class" will be handled later.
+ '("package" "import" "interface" "val" "var" "typealias"
+ "constructor" "companion" "init" "for" "do" "is"
+ ;; While we should insert a semicolon before "in" in a "when"
+ ;; expression or beginning of a expression, we should
+ ;; suppress a semicolon before "in" in "for", type
+ ;; parameters, or type arguments:
+ ;;
+ ;; when (x) {
+ ;; 1 -> 1 // implicit semicolon here
+ ;; in xs -> 2
+ ;; }
+ ;;
+ ;; // line breaks are prohibited before infix "in" operator.
+ ;; val x = 1 // implicit semicolon here
+ ;; in xs // Though this is an invalid expression anyway.
+ ;;
+ ;; for (
+ ;; x // no implicit semicolons here
+ ;; in
+ ;; xs
+ ;; ) {}
+ ;;
+ ;; Foo< // no implicit semicolons here
+ ;; in X
+ ;; >
+ ;;
+ ;; Because detecting the context is hard at lexer level, we
+ ;; omit semicolon for now, and handle it later in the
+ ;; indentation code.
+ ))
+ t)
+
+ ;; While or do-while
+ ((equal (kotlin-mode--token-text next-token) "while")
+ ;; insert semicolon unless it is a part of do-while.
+ (save-excursion (not (kotlin-mode--find-do-for-while))))
+
+ ;; "class" starts a new declaration unless preceded by "::".
+ ;;
+ ;; Example:
+ ;;
+ ;; class Foo { // This start a class declaration.
+ ;; }
+ ;;
+ ;; Foo ::
+ ;; class // This does not start a class declaration.
+ ((and (equal (kotlin-mode--token-text next-token) "class")
+ (not (equal (kotlin-mode--token-text previous-token) "::")))
+ t)
+
+ ;; Tokens that cannot start a statement/declaration.
+ ((or (memq
+ (kotlin-mode--token-type next-token)
+ '(implicit-\; string-chunk-after-template-expression))
+ (member
+ (kotlin-mode--token-text next-token)
+ '("*" "%" "/" "&" "&&" "||" ":" "=" "+=" "-=" "*=" "/=" "%="
+ "->" "." ".." "..<" "::" "?:" "?." "?" "<" ">" "<=" ">="
+ "!=" "!==" "==" "==="
+ "," ";" ")" "]" "}"
+ "as" "as?" "get" "set" "by" "where" "else" "catch" "finally"
+ ;; Because detecting the context is hard at lexer level,
+ ;; we omit semicolon before "in" for now, and handle it
+ ;; later in the indentation code.
+ "in"
+ ;; Strictly speaking, open curly bracket may start a statement
+ ;; as a part of lambda expression, it is rare case, so we
+ ;; suppress semicolon before it.
+ "{")))
+ nil)
+
+ ;; Open square bracket start a new statement unless preceded by a
+ ;; colon (already handled above).
+ ((eq (kotlin-mode--token-type next-token) '\[)
+ t)
+
+ ;; Open parenthesis start a new statement unless preceded by:
+ ;; - a colon (already handled above),
+ ;; - a comma (already handled above),
+ ;; - a dot (already handled above),
+ ;; - an equal sign (already handled above),
+ ;; - "->" (already handled above),
+ ;; - "is" (already handled above),
+ ;; - "!is" (already handled above),
+ ;; - "as" (already handled above),
+ ;; - "as?" (already handled above),
+ ;; - "if" (already handled above),
+ ;; - "while" (already handled above),
+ ;; - "for" (already handled above),
+ ;; - "when" (already handled above),
+ ;; - "catch" (already handled above),
+ ;; or is a part of:
+ ;; - secondary constructor declaration,
+ ;; - class declaration,
+ ;; - function declaration,
+ ;; - variable declaration,
+ ;; - getter declaration,
+ ;; - setter declaration,
+ ;; - constructor declaration call (example: constructor(): this(1)),
+ ;;
+ ;; Examples:
+ ;;
+ ;; public constructor() {}
+ ;;
+ ;; public class Foo() {}
+ ;; public class Foo () {}
+ ;; public class Foo public constructor ()
+ ;; public class Foo @AAA constructor ()
+ ;;
+ ;; fun foo() {}
+ ;; fun foo() {}
+ ;; fun (A).foo() {}
+ ;; fun A.B.C.foo() {}
+ ;; fun @AAA (A).foo() {}
+ ;; fun (A).foo() {}
+ ;; fun @AAA (A).foo() {}
+ ;; fun @AAA A.B.C.foo() {}
+ ;;
+ ;; fun () {}
+ ;; fun (A).() {}
+ ;; fun A.B.C.() {}
+ ;;
+ ;; val (x, y) = foo()
+ ;; var (x, y) = foo()
+ ;; val (x, y) = foo()
+ ;; val (A).(x, y) = foo()
+ ;; val (A).(x, y) = foo()
+ ;; val @AAA A.(x, y) = foo()
+ ;; val @AAA A.B.C.(x, y) = foo()
+ ;;
+ ;; get() {}
+ ;; set(value) {}
+ ;;
+ ;; constructor (): this(1)
+ ;; constructor (): super(1)
+ ;;
+ ;; Note that function argument cannot preceded by newline:
+ ;;
+ ;; val x = foo
+ ;; (bar()) // This is not a continuation of the previous line.
+ ((eq (kotlin-mode--token-type next-token) '\()
+ (and
+ (not (kotlin-mode--constructor-parameter-clause-p))
+ (not (kotlin-mode--function-parameter-clause-p))
+ (not (kotlin-mode--multi-variable-declaration-p))
+ (not (kotlin-mode--constructor-delegation-call-p))))
+
+ ;; Suppress implicit semicolon after the beginning of an interpolated
+ ;; expression.
+ ((eq (kotlin-mode--token-type previous-token)
+ 'string-chunk-before-interpolated-expression)
+ nil)
+
+ ;; Otherwise, inserts implicit semicolon.
+ (t t))))
+
+(defun kotlin-mode--close-parenthesis-before-control-structure-body-p (token)
+ "Return t if TOKEN is close parenthesis before control structure body.
+
+Return t if TOKEN is the close-parenthesis of \"if (...)\",
+\"while(...)\", or \"for (...)\", but not followed by \"{\".
+
+Return nil otherwise."
+ (and
+ (eq (kotlin-mode--token-type token) '\))
+ (save-excursion
+ (goto-char (kotlin-mode--token-end token))
+ (not (eq (kotlin-mode--token-type (kotlin-mode--forward-token-simple))
+ '\{)))
+ (save-excursion
+ (goto-char (kotlin-mode--token-end token))
+ (condition-case nil
+ (progn
+ (backward-list)
+ (let ((previous-token (kotlin-mode--backward-token-simple)))
+ (or
+ (member (kotlin-mode--token-text previous-token) '("if" "for"))
+ (and
+ (equal (kotlin-mode--token-text previous-token) "while")
+ (not (save-excursion (kotlin-mode--find-do-for-while)))))))
+ (scan-error
+ nil)))))
+
+(defun kotlin-mode--find-do-for-while ()
+ "Return \"do\" token for \"while\" token.
+
+If \"while\" is not part of do-while, return nil.
+
+Assuming the point is before \"while\" token."
+ ;; Example:
+ ;;
+ ;; do
+ ;; while (false)
+ ;; print(1)
+ ;; while (false)
+ ;; print(2)
+ ;;
+ ;; is parsed as three statements:
+ ;;
+ ;; do while (false);
+ ;; print(1);
+ ;; while (false) print(2);
+ ;;
+ ;; So we don't need to worry about bare while-statement in body of do-while.
+ (let ((nesting-level 1)
+ previous-token)
+ (while (< 0 nesting-level)
+ (setq previous-token (kotlin-mode--backward-token-or-list t))
+ (cond
+ ((equal (kotlin-mode--token-text previous-token) "while")
+ (setq nesting-level (1+ nesting-level)))
+
+ ((equal (kotlin-mode--token-text previous-token) "do")
+ (setq nesting-level (1- nesting-level)))
+
+ ((memq (kotlin-mode--token-type previous-token) '(outside-of-buffer {))
+ ;; Unmatched
+ (setq nesting-level 0))))
+ (and (equal (kotlin-mode--token-text previous-token) "do")
+ previous-token)))
+
+(defun kotlin-mode--constructor-parameter-clause-p ()
+ "Return non-nil if the point is before parameters of constructor declaration."
+ ;; Examples:
+ ;;
+ ;; public constructor() {}
+ ;;
+ ;; public class Foo() {}
+ ;; public class Foo () {}
+ ;; public class Foo public constructor ()
+ ;; public class Foo @AAA constructor ()
+ (let ((previous-token (save-excursion (kotlin-mode--backward-token-simple))))
+ (or
+ (equal (kotlin-mode--token-text previous-token) "constructor")
+ (save-excursion
+ (kotlin-mode--try-backward-type-parameters)
+ (and
+ (eq (kotlin-mode--token-type (kotlin-mode--backward-token-simple))
+ 'atom)
+ (equal (kotlin-mode--token-text (kotlin-mode--backward-token-simple))
+ "class"))))))
+
+(defun kotlin-mode--function-parameter-clause-p ()
+ "Return non-nil if the point is before parameters of function declaration."
+ ;; Examples:
+ ;;
+ ;; fun foo() {}
+ ;; fun foo() {}
+ ;; fun (A).foo() {}
+ ;; fun A.B.C.foo() {}
+ ;; fun @AAA (A).foo() {}
+ ;; fun (A).foo() {}
+ ;; fun @AAA (A).foo() {}
+ ;; fun @AAA A.B.C.foo() {}
+ ;;
+ ;; fun () {}
+ ;; fun (A).() {}
+ ;; fun A.B.C.() {}
+ (let ((previous-token (save-excursion (kotlin-mode--backward-token-simple))))
+ (cond
+ ;; fun () {}
+ ;; fun (A).() {} // for first parenthesis
+ ;; fun (A).foo() {} // for first parenthesis
+ ((equal (kotlin-mode--token-text previous-token) "fun")
+ t)
+
+ ;; fun (A).() {}
+ ;; fun A.B.C.() {}
+ ((equal (kotlin-mode--token-text previous-token) ".")
+ (save-excursion
+ (and (kotlin-mode--try-backward-receiver-type)
+ (equal (kotlin-mode--token-text
+ (kotlin-mode--backward-token-simple))
+ "fun"))))
+
+ ;; fun @AAA (A).foo() {} // for first parenthesis
+ ;; fun (A).foo() {} // for first parenthesis
+ ;; fun @AAA (A).foo() {} // for first parenthesis
+ ((save-excursion
+ (kotlin-mode--try-backward-type-modifiers)
+ (kotlin-mode--try-backward-type-parameters)
+ (equal (kotlin-mode--token-text
+ (kotlin-mode--backward-token-simple))
+ "fun"))
+ t)
+
+ ;; fun foo() {}
+ ;; fun foo() {}
+ ;; fun (A).foo() {} // for second parenthesis
+ ;; fun A.B.C.foo() {}
+ ;; fun @AAA (A).foo() {} // for second parenthesis
+ ;; fun (A).foo() {} // for second parenthesis
+ ;; fun @AAA (A).foo() {} // for second parenthesis
+ ;; fun @AAA A.B.C.foo() {}
+ ((eq (kotlin-mode--token-type previous-token) 'atom)
+ (save-excursion
+ (goto-char (kotlin-mode--token-start previous-token))
+ (kotlin-mode--try-backward-receiver-type)
+ (kotlin-mode--try-backward-type-parameters)
+ (equal (kotlin-mode--token-text
+ (kotlin-mode--backward-token-simple))
+ "fun"))))))
+
+(defun kotlin-mode--try-backward-receiver-type ()
+ "Try backward dot, type, and type modifiers.
+
+Return non-nil if succeeds. Keep position and return nil otherwise."
+ (let ((pos (point))
+ (previous-token (kotlin-mode--backward-token-simple)))
+ (if (and (equal (kotlin-mode--token-text previous-token) ".")
+ (kotlin-mode--try-backward-type))
+ (progn
+ (kotlin-mode--try-backward-type-modifiers)
+ t)
+ (goto-char pos)
+ nil)))
+
+(defun kotlin-mode--try-backward-type ()
+ "Try backward type but not type modifiers.
+
+Return non-nil if succeeds. Keep position and return nil otherwise."
+ ;; type
+ ;; : typeModifiers?
+ ;; ( parenthesizedType
+ ;; | nullableType
+ ;; | typeReference
+ ;; | functionType)
+ ;; ;
+ ;;
+ ;; typeReference
+ ;; : userType
+ ;; | DYNAMIC
+ ;; ;
+ ;;
+ ;; userType
+ ;; : simpleUserType (NL* DOT NL* simpleUserType)*
+ ;; ;
+ ;;
+ ;; simpleUserType
+ ;; : simpleIdentifier (NL* typeArguments)?
+ ;; ;
+ ;;
+ ;; functionType
+ ;; : (receiverType NL* DOT NL*)? functionTypeParameters NL* ARROW NL* type
+ ;; ;
+ (let ((pos (point))
+ (previous-token (kotlin-mode--backward-token-simple))
+ result)
+ (setq
+ result
+ (cond
+ ;; parenthesizedType
+ ((equal (kotlin-mode--token-text previous-token) ")")
+ (goto-char (kotlin-mode--token-end previous-token))
+ (condition-case nil
+ (progn
+ (backward-list)
+ t)
+ (scan-error
+ (goto-char pos)
+ nil)))
+
+ ;; nullableType
+ ((equal (kotlin-mode--token-text previous-token) "?")
+ (kotlin-mode--try-backward-type))
+
+ ;; typeReference
+ (t
+ (goto-char (kotlin-mode--token-end previous-token))
+ (kotlin-mode--try-backward-type-reference))))
+ ;; functionType
+ (while (and result
+ (equal (kotlin-mode--token-text
+ (save-excursion (kotlin-mode--backward-token-simple)))
+ "->"))
+ ;; Skip "->"
+ (kotlin-mode--backward-token-simple)
+ ;; Skip parameters and a receiver type if any.
+ (setq result (condition-case nil
+ (progn
+ (backward-list)
+ (kotlin-mode--try-backward-receiver-type)
+ t)
+ (scan-error
+ nil))))
+ (unless result
+ (goto-char pos))
+ result))
+
+(defun kotlin-mode--try-forward-type-reference ()
+ "Try forward type reference.
+
+Return non-nil if succeeds. Keep position and return nil otherwise."
+ ;; typeReference
+ ;; : userType
+ ;; | DYNAMIC
+ ;; ;
+ ;; userType
+ ;; : simpleUserType (NL* DOT NL* simpleUserType)*
+ ;; ;
+ ;;
+ ;; simpleUserType
+ ;; : simpleIdentifier (NL* typeArguments)?
+ ;; ;
+ (let ((pos (point))
+ result)
+ (setq result (kotlin-mode--forward-simple-user-type))
+ (while (and result
+ (equal (kotlin-mode--token-text
+ (save-excursion (kotlin-mode--forward-token-simple)))
+ "."))
+ ;; Skip "."
+ (kotlin-mode--forward-token-simple)
+ (setq result (kotlin-mode--forward-simple-user-type)))
+ (unless result
+ (goto-char pos))
+ result))
+
+(defun kotlin-mode--forward-simple-user-type ()
+ "Forward simple user type.
+
+Return non-nil if succeeds. Otherwise, return nil and the point is undefined."
+ ;; simpleUserType
+ ;; : simpleIdentifier (NL* typeArguments)?
+ ;; ;
+ (let ((next-token (save-excursion (kotlin-mode--forward-token-simple))))
+ (if (eq (kotlin-mode--token-type next-token) 'atom)
+ ;; Found simpleIdentifier
+ (progn
+ (goto-char (kotlin-mode--token-end next-token))
+ (when (equal (kotlin-mode--token-text
+ (save-excursion (kotlin-mode--forward-token-simple)))
+ "<")
+ ;; Maybe type paramters
+ (kotlin-mode--try-forward-type-parameters))
+ t)
+ ;; Not a simpleUserType
+ nil)))
+
+(defun kotlin-mode--try-backward-type-reference ()
+ "Try backward type reference.
+
+Return non-nil if succeeds. Keep position and return nil otherwise."
+ ;; typeReference
+ ;; : userType
+ ;; | DYNAMIC
+ ;; ;
+ ;; userType
+ ;; : simpleUserType (NL* DOT NL* simpleUserType)*
+ ;; ;
+ ;;
+ ;; simpleUserType
+ ;; : simpleIdentifier (NL* typeArguments)?
+ ;; ;
+ (let ((pos (point))
+ result)
+ (setq result (kotlin-mode--backward-simple-user-type))
+ (while (and result
+ (equal (kotlin-mode--token-text
+ (save-excursion (kotlin-mode--backward-token-simple)))
+ "."))
+ ;; Skip "."
+ (kotlin-mode--backward-token-simple)
+ (setq result (kotlin-mode--backward-simple-user-type)))
+ (unless result
+ (goto-char pos))
+ result))
+
+(defun kotlin-mode--backward-simple-user-type ()
+ "Backward simple user type.
+
+Return non-nil if succeeds. Otherwise, return nil and the point is undefined."
+ ;; simpleUserType
+ ;; : simpleIdentifier (NL* typeArguments)?
+ ;; ;
+ (let ((pos (point))
+ (previous-token (save-excursion (kotlin-mode--backward-token-simple))))
+ (cond
+ ;; simpleUserType with type arguments
+ ((equal (kotlin-mode--token-text previous-token) ">")
+ (if (and (kotlin-mode--try-backward-type-parameters)
+ (eq (kotlin-mode--token-type
+ (kotlin-mode--backward-token-simple))
+ 'atom))
+ t
+ (goto-char pos)
+ nil))
+
+ ;; Start of an annotation
+ ((eq (kotlin-mode--token-type previous-token) 'annotation)
+ (goto-char (kotlin-mode--token-start previous-token))
+ (forward-char)
+ t)
+
+ ;; Other simpleUserType
+ ((eq (kotlin-mode--token-type previous-token) 'atom)
+ (goto-char (kotlin-mode--token-start previous-token))
+ t)
+
+ ;; Not a simpleUserType
+ (t
+ nil))))
+
+(defun kotlin-mode--try-backward-type-modifiers ()
+ "Try backward type modifiers.
+
+Keep position if failed."
+ (let ((previous-token (save-excursion (kotlin-mode--backward-token-simple))))
+ (while (or
+ (eq (kotlin-mode--token-type previous-token) 'annotation)
+ (equal (kotlin-mode--token-text previous-token) "suspend"))
+ (goto-char (kotlin-mode--token-start previous-token))
+ (setq previous-token
+ (save-excursion (kotlin-mode--backward-token-simple))))))
+
+(defun kotlin-mode--multi-variable-declaration-p ()
+ "Return non-nil if the point is before multi variable declaration."
+ ;; Example:
+ ;;
+ ;; val (x, y) = foo()
+ ;; var (x, y) = foo()
+ ;; val (x, y) = foo()
+ ;; val (A).(x, y) = foo()
+ ;; val (A).(x, y) = foo()
+ ;; val @AAA A.(x, y) = foo()
+ ;; val @AAA A.B.C.(x, y) = foo()
+ (save-excursion
+ (kotlin-mode--try-backward-receiver-type)
+ (kotlin-mode--try-backward-type-parameters)
+ (member (kotlin-mode--token-text
+ (kotlin-mode--backward-token-simple))
+ '("val" "var"))))
+
+(defun kotlin-mode--constructor-delegation-call-p ()
+ "Return non-nil if the point is before constructor delegation call arguments."
+ ;; Example:
+ ;;
+ ;; constructor (): this(1)
+ ;; constructor (): super(1)
+ (save-excursion
+ (and (member (kotlin-mode--token-text (kotlin-mode--backward-token-simple))
+ '("this" "super"))
+ (eq (kotlin-mode--token-type (kotlin-mode--backward-token-simple))
+ ':))))
+
+(defun kotlin-mode--try-forward-modifiers ()
+ "Try forward modifiers.
+
+Keep position if failed."
+ (let ((next-token (save-excursion (kotlin-mode--forward-token-simple))))
+ (while (or
+ (eq (kotlin-mode--token-type next-token) 'annotation)
+ (member (kotlin-mode--token-text next-token)
+ kotlin-mode--modifier-keywords))
+ (goto-char (kotlin-mode--token-end next-token))
+ (setq next-token (save-excursion (kotlin-mode--forward-token-simple))))))
+
+(defun kotlin-mode--try-backward-modifiers ()
+ "Try forward modifiers.
+
+Keep position if failed."
+ (let ((previous-token (save-excursion (kotlin-mode--backward-token-simple))))
+ (while (or
+ (eq (kotlin-mode--token-type previous-token) 'annotation)
+ (member (kotlin-mode--token-text previous-token)
+ kotlin-mode--modifier-keywords))
+ (goto-char (kotlin-mode--token-start previous-token))
+ (setq previous-token
+ (save-excursion (kotlin-mode--backward-token-simple))))))
+
+(defun kotlin-mode--backward-token-or-list (&optional use-backward-token-simple)
+ "Move point to the start position of the previous token or list.
+
+Return the token skipped.
+
+If USE-BACKWARD-TOKEN-SIMPLE is non-nil, use
+`kotlin-mode--backward-token-simple' instead of
+`kotlin-mode--backward-token'."
+ (let* ((previous-token (if use-backward-token-simple
+ (kotlin-mode--backward-token-simple)
+ (kotlin-mode--backward-token)))
+ (previous-type (kotlin-mode--token-type previous-token))
+ (previous-text (kotlin-mode--token-text previous-token))
+ (previous-start (kotlin-mode--token-start previous-token))
+ (previous-end (kotlin-mode--token-end previous-token)))
+ (cond
+ ;; Maybe list
+ ((memq previous-type '(} \) \] \)-before-control-structure-body))
+ (goto-char previous-end)
+ (condition-case nil
+ (progn
+ (backward-list)
+ (make-instance
+ 'kotlin-mode--token
+ :type (assoc-default
+ previous-type
+ '((} . {})
+ (\) . \(\))
+ (\)-before-control-structure-body .
+ \(\)-before-control-structure-body)
+ (\] . \[\])))
+ :text (buffer-substring-no-properties (point) previous-end)
+ :start (point)
+ :end previous-end))
+ (scan-error
+ (goto-char previous-start)
+ previous-token)))
+
+ ;; Maybe type parameter list
+ ((equal previous-text ">")
+ (goto-char previous-end)
+ (if (kotlin-mode--try-backward-type-parameters)
+ (make-instance
+ 'kotlin-mode--token
+ :type '<>
+ :text (buffer-substring-no-properties (point) previous-end)
+ :start (point)
+ :end previous-end)
+ (goto-char previous-start)
+ previous-token))
+
+ ;; Other token
+ (t previous-token))))
+
+(defun kotlin-mode--forward-token-or-list (&optional use-forward-token-simple)
+ "Move point to the end position of the next token or list.
+
+Return the token skipped.
+
+If USE-FORWARD-TOKEN-SIMPLE is non-nil, use
+`kotlin-mode--forward-token-simple' instead of
+`kotlin-mode--forward-token'."
+ (let* ((next-token (if use-forward-token-simple
+ (kotlin-mode--forward-token-simple)
+ (kotlin-mode--forward-token)))
+ (next-type (kotlin-mode--token-type next-token))
+ (next-text (kotlin-mode--token-text next-token))
+ (next-start (kotlin-mode--token-start next-token))
+ (next-end (kotlin-mode--token-end next-token))
+ result-type)
+ (cond
+ ;; Maybe list
+ ((memq next-type '({ \( \[))
+ (goto-char next-start)
+ (condition-case nil
+ (progn
+ (forward-list)
+ (setq
+ result-type
+ (if (kotlin-mode--close-parenthesis-before-control-structure-body-p
+ (save-excursion (kotlin-mode--backward-token-simple)))
+ '\(\)-before-control-structure-body
+ (assoc-default next-type '(({ . {})
+ (\( . \(\))
+ (\[ . \[\])))))
+ (make-instance
+ 'kotlin-mode--token
+ :type result-type
+ :text (buffer-substring-no-properties next-start (point))
+ :start next-start
+ :end (point)))
+ (scan-error
+ (goto-char next-end)
+ next-token)))
+
+ ;; Maybe type parameter list
+ ((equal next-text "<")
+ (goto-char next-start)
+ (if (kotlin-mode--try-forward-type-parameters)
+ (make-instance
+ 'kotlin-mode--token
+ :type '<>
+ :text (buffer-substring-no-properties next-start (point))
+ :start next-start
+ :end (point))
+ (goto-char next-end)
+ next-token))
+
+ ;; Other token
+ (t next-token))))
+
+(defun kotlin-mode--try-backward-type-parameters ()
+ "Move point to the start of the type parameter list.
+
+Return non-nil if succeeds. Keep position and return nil otherwise.
+
+It is a type parameter list if:
+- it has matching angle brackets, and
+- it does not have tokens that cannot appears in a type parameter list."
+ (let ((pos (point)))
+ (if (and
+ (equal (kotlin-mode--token-text (kotlin-mode--backward-token-simple))
+ ">")
+ (kotlin-mode--try-skip-type-parameters
+ (lambda () (kotlin-mode--backward-token-or-list t))
+ "<" ">"))
+ t
+ (goto-char pos)
+ nil)))
+
+(defun kotlin-mode--try-forward-type-parameters ()
+ "Move point to the end of the type parameter list.
+
+Return non-nil if succeeds. Keep position and return nil otherwise.
+
+It is a type parameter list if:
+- it has matching angle brackets, and
+- it does not have tokens that cannot appears in a type parameter list."
+ (let ((pos (point)))
+ (if (and
+ (equal (kotlin-mode--token-text (kotlin-mode--forward-token-simple))
+ "<")
+ (kotlin-mode--try-skip-type-parameters
+ (lambda () (kotlin-mode--forward-token-or-list t))
+ ">" "<"))
+ t
+ (goto-char pos)
+ nil)))
+
+(defconst kotlin-mode--tokens-not-in-type-parameter-list
+ ;; Whitelisting tend to be fragile. So we list tokens that are
+ ;; unlikely to appear in type parameter lists in the current
+ ;; version and future ones.
+ ;;
+ ;; Example of type parameters:
+ ;; <
+ ;; A: B,
+ ;; @AAA(aaa) reified in C: @AAA suspend D.(x: Int) -> (X)?
+ ;; >
+ ;;
+ ;; Example of type arguments
+ ;; <
+ ;; A,
+ ;; @AAA(aaa) in *,
+ ;; suspend D.(x: Int) -> (X)?
+ ;; >
+ ;;
+ ;; We don't need to consider the contents of inner brackets because
+ ;; they are skipped by `kotlin-mode--backward-token-or-list'.
+ ;;
+ ;; String literals and numbers are also excluded by
+ ;; `kotlin-mode--try-skip-type-parameters'.
+ `(outside-of-buffer
+ \;
+ { } \( \) \[ \]
+ "%" "/" "+" "-"
+ "++" "--"
+ "&&" "||"
+ "!==" "!="
+ ;; exclude "in"
+ "is" "!is" "!in" "as" "as?"
+ "!"
+ "=" "+=" "-=" "*=" "/=" "%="
+ ".." "..<" "::" "?:" "?." "<=" ">=" "==" "==="
+ "package" "import" "class" "interface" "fun" "object" "val" "var"
+ "typealias" "constructor" "by" "companion" "init" "if" "else" "when"
+ "try" "catch" "finally" "for" "do" "while" "throw"
+ return continue break "true" "false" "null"
+ ,@(remove "suspend" kotlin-mode--modifier-keywords)))
+
+(defun kotlin-mode--try-skip-type-parameters
+ (skip-token-or-list-function matching-bracket-text unmatching-bracket-text)
+ "Skip type parameters if the point is just before/after one.
+
+Return non-nil if succeeds. Keep position and return nil otherwise.
+
+Assuming open/close bracket is already skipped.
+
+SKIP-TOKEN-OR-LIST-FUNCTION is a function skipping forward/backward a
+token or a list.
+MATCHING-BRACKET-TEXT is a text of the matching bracket.
+UNMATCHING-BRACKET-TEXT is a text of the current bracket."
+ (let ((pos (point))
+ result
+ (prohibited-tokens (cons
+ unmatching-bracket-text
+ kotlin-mode--tokens-not-in-type-parameter-list))
+ (next-token (funcall skip-token-or-list-function)))
+ (while
+ (cond
+ ;; Prohibited tokens
+ ((or
+ (memq (kotlin-mode--token-type next-token) prohibited-tokens)
+ (member (kotlin-mode--token-text next-token) prohibited-tokens)
+ (string-match-p "^[\"'0-9]" (kotlin-mode--token-text next-token)))
+ ;; Not a type parameter list.
+ ;; Return to the initial position and stop the loop.
+ (goto-char pos)
+ (setq result nil)
+ nil)
+
+ ;; Matching bracket
+ ((equal (kotlin-mode--token-text next-token) matching-bracket-text)
+ ;; Found the matching open angle bracket. Stop the loop.
+ (setq result t)
+ nil)
+
+ ;; Otherwise, keep scanning
+ (t t))
+ (setq next-token (funcall skip-token-or-list-function)))
+ result))
+
+(defun kotlin-mode--forward-token ()
+ "Move point forward to the next position of the end of a token.
+
+Return the token object. If no more tokens available, return a token with
+type `outside-of-buffer'."
+ (let ((pos (point)))
+ ;; Skip comments and whitespaces.
+ (let ((chunk (kotlin-mode--chunk-after)))
+ ;; If point is inside a comment, go back to the beginning of the
+ ;; comment before skipping it.
+ (when (kotlin-mode--chunk-comment-p chunk)
+ (goto-char (kotlin-mode--chunk-start chunk))))
+ (forward-comment (point-max))
+ (cond
+ ;; Outside of buffer
+ ((eobp)
+ (make-instance 'kotlin-mode--token
+ :type 'outside-of-buffer
+ :text ""
+ :start (point)
+ :end (point)))
+
+ ;; Implicit semicolon
+ ((and
+ ;; Check (forward-comment (point-max)) skipped a newline.
+ (< pos
+ (save-excursion
+ (kotlin-mode--goto-non-comment-bol)
+ (point)))
+ (save-excursion (goto-char pos) (kotlin-mode--implicit-semi-p)))
+ (make-instance 'kotlin-mode--token
+ :type 'implicit-\;
+ :text (buffer-substring-no-properties pos (point))
+ :start pos
+ :end (point)))
+
+ (t
+ (let ((token (kotlin-mode--extend-annotation-token-forward
+ (kotlin-mode--forward-token-simple))))
+ (cond
+ ;; Close parenthesis of "if ()", "while ()", or "for ()"
+ ((kotlin-mode--close-parenthesis-before-control-structure-body-p token)
+ (make-instance 'kotlin-mode--token
+ :type '\)-before-control-structure-body
+ :text (kotlin-mode--token-text token)
+ :start (kotlin-mode--token-start token)
+ :end (kotlin-mode--token-end token)))
+
+ ;; "else" not followed by "if" on the same line or "{"
+ ((kotlin-mode--bare-else-p token)
+ (make-instance 'kotlin-mode--token
+ :type 'bare-else
+ :text (kotlin-mode--token-text token)
+ :start (kotlin-mode--token-start token)
+ :end (kotlin-mode--token-end token)))
+
+ ;; super@label
+ ((save-excursion
+ (and (equal (kotlin-mode--token-text token) "super")
+ (eq (char-after) ?<)
+ (kotlin-mode--try-forward-type-parameters)
+ (eq (char-after) ?@)))
+ (kotlin-mode--try-forward-type-parameters)
+ (forward-char)
+ (skip-syntax-forward "w")
+ (make-instance 'kotlin-mode--token
+ :type 'super
+ :text (buffer-substring-no-properties
+ (kotlin-mode--token-start token)
+ (point))
+ :start (kotlin-mode--token-start token)
+ :end (point)))
+
+ ;; "->" of lambda parameters
+ ((kotlin-mode--anonymous-function-parameter-arrow-p token)
+ (make-instance 'kotlin-mode--token
+ :type 'anonymous-function-parameter-arrow
+ :text (kotlin-mode--token-text token)
+ :start (kotlin-mode--token-start token)
+ :end (kotlin-mode--token-end token)))
+
+ ;; "->" of when expression
+ ((kotlin-mode--when-expression-arrow-p token)
+ (make-instance 'kotlin-mode--token
+ :type 'when-expression-arrow
+ :text (kotlin-mode--token-text token)
+ :start (kotlin-mode--token-start token)
+ :end (kotlin-mode--token-end token)))
+
+ (t token)))))))
+
+(defun kotlin-mode--bare-else-p (token)
+ "Return non-nil if TOKEN is bare \"else\" token.
+
+A bare \"else\" token is a \"else\" token not followed by \"if\" on
+the same line, \"->\", or \"{\"."
+ (let ((next-token (save-excursion
+ (goto-char (kotlin-mode--token-end token))
+ (kotlin-mode--forward-token-simple))))
+ (and (equal (kotlin-mode--token-text token) "else")
+ (not (eq (kotlin-mode--token-type next-token) '{))
+ (not (eq (kotlin-mode--token-text next-token) "->"))
+ (not (and
+ (equal (kotlin-mode--token-text next-token) "if")
+ (save-excursion
+ (= (progn
+ (goto-char (kotlin-mode--token-start next-token))
+ (kotlin-mode--goto-non-comment-bol)
+ (point))
+ (progn
+ (goto-char (kotlin-mode--token-start token))
+ (kotlin-mode--goto-non-comment-bol)
+ (point)))))))))
+
+(defun kotlin-mode--anonymous-function-parameter-arrow-p (token)
+ "Return non-nil if TOKEN is an arrow of lambda parameters."
+ ;; Examples:
+ ;;
+ ;; { -> 1}
+ ;; { x -> 1 }
+ ;; { f: (Int) -> Int, x: Int -> f(x) }
+ ;; { f: (Int) -> Int -> f(1) }
+ ;; { f: (Int) -> (Int) -> Int -> f(1) }
+ ;; { (x: Int, y: Int) -> x + y }
+ ;;
+ ;; If an arrow is not preceded by a close parenthesis, it is an end
+ ;; of parameters.
+ ;;
+ ;; If an arrow is preceded by a close parenthesis and the
+ ;; parentheses are preceded by an open curly bracket, the arrow is
+ ;; an end of parameters.
+ ;;
+ ;; Otherwise, it is an arrow in function types.
+ ;;
+ ;; Furthermore, the curly open bracket should not be preceded by
+ ;; "when ()".
+ (and (equal (kotlin-mode--token-text token) "->")
+ (save-excursion
+ (goto-char (kotlin-mode--token-start token))
+ (forward-comment (- (point)))
+ (or
+ ;; The arrow is not preceded by a close parenthesis.
+ ;;
+ ;; Examples:
+ ;; { -> 1}
+ ;; { x -> 1 }
+ ;; { f: (Int) -> Int, x: Int -> f(x) } // last arrow
+ ;; { f: (Int) -> Int -> f(1) } // last arrow
+ ;; { f: (Int) -> (Int) -> Int -> f(1) } // last arrow
+ (not (eq (char-before) ?\)))
+ ;; The parentheses are preceded by an open curly bracket.
+ ;;
+ ;; Example:
+ ;; { (x: Int, y: Int) -> x + y }
+ (condition-case nil
+ (progn (backward-list)
+ (forward-comment (- (point)))
+ (eq (char-before) ?{))
+ (scan-error nil))))
+ (save-excursion
+ ;; The token is inside a curly brackets but not inside a
+ ;; when-expression.
+ (goto-char (kotlin-mode--token-start token))
+ (let ((containing-bracket
+ (kotlin-mode--find-containing-brackets (point))))
+ (and (eq (car containing-bracket) '{)
+ (not (kotlin-mode--inside-when-expression-p
+ containing-bracket)))))))
+
+(defun kotlin-mode--when-expression-arrow-p (token)
+ "Return non-nil if TOKEN is an arrow of when expression."
+ ;; This function does not distinguish arrows of when expression and
+ ;; ones of function types.
+ (and (equal (kotlin-mode--token-text token) "->")
+ (save-excursion
+ (goto-char (kotlin-mode--token-start token))
+ (kotlin-mode--inside-when-expression-p))))
+
+(defun kotlin-mode--inside-when-expression-p (&optional containing-bracket)
+ "Return non-nil if point is inside a when expression.
+
+CONTAINING-BRACKET is a return value of
+`kotlin-mode--find-containing-brackets'. If ommitted,
+\(kotlin-mode--find-containing-brackets (point)) is used."
+ (unless containing-bracket
+ (setq containing-bracket (kotlin-mode--find-containing-brackets (point))))
+ (goto-char (cdr containing-bracket))
+ (and (eq (car containing-bracket) '{)
+ (eq (kotlin-mode--token-type (kotlin-mode--backward-token-or-list t))
+ '\(\))
+ (equal (kotlin-mode--token-text (kotlin-mode--backward-token-simple))
+ "when")))
+
+(defun kotlin-mode--forward-token-simple ()
+ "Like `kotlin-mode--forward-token' without recursion.
+
+This function does not return `implicit-;'."
+ (forward-comment (point-max))
+ (cond
+ ;; Outside of buffer
+ ((eobp)
+ (make-instance 'kotlin-mode--token
+ :type 'outside-of-buffer
+ :text ""
+ :start (point)
+ :end (point)))
+
+ ;; End of template expression
+ ((and (eq (char-after) ?\})
+ (equal (get-text-property (point) 'syntax-table)
+ (string-to-syntax "|")))
+ (let ((pos-after-comment (point)))
+ (kotlin-mode--forward-string-chunk)
+ (make-instance
+ 'kotlin-mode--token
+ :type 'string-chunk-after-template-expression
+ :text (buffer-substring-no-properties pos-after-comment (point))
+ :start pos-after-comment
+ :end (point))))
+
+ ;; ::
+ ((looking-at "::")
+ (forward-char 2)
+ (make-instance 'kotlin-mode--token
+ :type 'operator
+ :text "::"
+ :start (- (point) 2)
+ :end (point)))
+
+ ;; Separators and parentheses
+ ((memq (char-after) '(?, ?\; ?\{ ?\} ?\[ ?\] ?\( ?\) ?:))
+ (forward-char)
+ (make-instance 'kotlin-mode--token
+ :type (intern (string (char-before)))
+ :text (string (char-before))
+ :start (1- (point))
+ :end (point)))
+
+ ;; Open angle bracket for type parameters or type arguments
+ ;;
+ ;; We use a heuristic: spaces are inserted around inequality sign, but not
+ ;; for angle bracket, and a type parameter starts with upper case
+ ;; characters, parentheses, asterisk, keyword 'reified', keyword 'in',
+ ;; keyword 'out', keyword 'suspend', or annotations.
+ ((and (eq (char-after) ?<)
+ (looking-at
+ (rx (seq "<" (or (any "_(@[*" upper)
+ (seq (or "reified" "in" "out" "suspend")
+ word-end))))))
+ (forward-char)
+ (make-instance 'kotlin-mode--token
+ :type '<
+ :text "<"
+ :start (1- (point))
+ :end (point)))
+
+ ;; Close angle bracket for type parameters or type arguments
+ ;;
+ ;; Close angle bracket follows identifiers, parentheses, question symbols,
+ ;; or other angle brackets (e.g. Foo>)
+ ((and (eq (char-after) ?>)
+ (save-excursion
+ ;; You know that regular languages can be reversed. Thus you may
+ ;; think that `looking-back' reverses the given regexp and scans
+ ;; chars backwards. Nevertheless, `looking-back' function does not
+ ;; do that. It just repeats `looking-at' with decrementing start
+ ;; position until it succeeds. The document says that it is not
+ ;; recommended to use. So we use combination of
+ ;; `skip-chars-backward', `skip-syntax-backward', and
+ ;; `looking-at' here.
+ (skip-chars-backward "?)>")
+ (skip-syntax-backward "w")
+ (looking-at "[[:upper:]_]")))
+ (forward-char)
+ (make-instance 'kotlin-mode--token
+ :type '>
+ :text ">"
+ :start (1- (point))
+ :end (point)))
+
+ ;; Operator (other than as, in, or is)
+ ((looking-at
+ (rx
+ (or
+ "++" "--"
+ "&&" "||"
+ "!==" "!="
+ (seq "!is" word-end) (seq "!in" word-end) "as?"
+ "!"
+ "->"
+ "..<" ".." "." "?:" "?." "?" "<" ">" "<=" ">=" "===" "=="
+ "=" "+=" "-=" "*=" "/=" "%="
+ "*" "%" "/" "+" "-" "&")))
+ (let ((text (match-string-no-properties 0))
+ (start (match-beginning 0))
+ (end (match-end 0)))
+ (goto-char end)
+ (make-instance 'kotlin-mode--token
+ :type 'operator
+ :text text
+ :start start
+ :end end)))
+
+ ;; Backquoted identifier or character literal
+ ((memq (char-after) '(?` ?'))
+ (let ((pos-after-comment (point)))
+ (kotlin-mode--forward-string-chunk)
+ (make-instance
+ 'kotlin-mode--token
+ :type 'atom
+ :text (buffer-substring-no-properties pos-after-comment (point))
+ :start pos-after-comment
+ :end (point))))
+
+ ;; String
+ ((looking-at "\"")
+ (let ((pos-after-comment (point)))
+ (forward-char)
+ (kotlin-mode--end-of-string)
+ (make-instance
+ 'kotlin-mode--token
+ :type 'atom
+ :text (buffer-substring-no-properties pos-after-comment (point))
+ :start pos-after-comment
+ :end (point))))
+
+ ;; Part of annotations. Only @ and first identifier.
+ ;;
+ ;; This will be augmented by
+ ;; `kotlin-mode--extend-annotation-token-forward' later.
+ ((eq (char-after) ?@)
+ (let ((pos-after-comment (point)))
+ (forward-char)
+ (if (eq (char-after) ?`)
+ (kotlin-mode--forward-string-chunk)
+ (skip-syntax-forward "w"))
+ ;; Strictly speaking, single at sign is not an annotation, but
+ ;; we treat it as an annotation.
+ (make-instance
+ 'kotlin-mode--token
+ :type 'annotation
+ :text (buffer-substring-no-properties pos-after-comment (point))
+ :start pos-after-comment
+ :end (point))))
+
+ ;; Other tokens including identifiers, keywords, labels, and numbers.
+ ;;
+ ;; Note that we parse 123.456e+2 as "123" "." "456e" "+" "2".
+ (t
+ (let* ((pos-after-comment (point))
+ (text
+ (cond
+ ;; return@label, continue@label, break@label, this@label,
+ ;; or super@label.
+ ((looking-at (rx (or "return@"
+ "continue@"
+ "break@"
+ "this@"
+ "super@")))
+ (skip-syntax-forward "w")
+ (forward-char)
+ (skip-syntax-forward "w")
+ (buffer-substring-no-properties pos-after-comment (point)))
+
+ ;; Identifiers, keywords, labels, or numbers
+ ((eq (syntax-class (syntax-after (point)))
+ (syntax-class (string-to-syntax "w")))
+ (skip-syntax-forward "w")
+ ;; Skip an at sign if exists for label.
+ (when (eq (char-after) ?@)
+ (forward-char))
+ (buffer-substring-no-properties pos-after-comment (point)))
+ ;; Unknown character type. Treats as a single-letter token.
+ (t
+ (forward-char)
+ (string (char-before)))))
+ (type (cond
+ ((member text '("is" "in" "as"))
+ ;; Note that "as?" is already handled.
+ 'operator)
+
+ ((eq (aref text (1- (length text))) ?@)
+ 'label)
+
+ ((string-match "^return\\>" text)
+ 'return)
+
+ ((string-match "^continue\\>" text)
+ 'return)
+
+ ((string-match "^break\\>" text)
+ 'break)
+
+ ((string-match "^this\\>" text)
+ 'this)
+
+ ((string-match "^super\\>" text)
+ 'super)
+
+ (t
+ 'atom))))
+ (make-instance 'kotlin-mode--token
+ :type type
+ :text text
+ :start (- (point) (length text))
+ :end (point))))))
+
+(defun kotlin-mode--extend-annotation-token-forward (token)
+ "Return annotation token if TOKEN is the start of an annotation.
+
+TOKEN is assumed to be a return value of `kotlin-mode--forward-token-simple'."
+ (if (eq (kotlin-mode--token-type token) 'annotation)
+ (let ((pos (point)))
+ (cond
+ ;; Example: @file:Abc(def)
+ ;; Example: @file:[Abc Def Ghi]
+ ((save-excursion
+ (forward-comment (point-max))
+ (eq (char-after) ?:))
+ (forward-comment (point-max))
+ (forward-char)
+ (unless (or (kotlin-mode--try-forward-multi-annotation-bracket)
+ (kotlin-mode--try-forward-unescaped-annotation))
+ (goto-char pos)))
+
+ ;; Example: @[Abc Def Ghi]
+ ((equal (kotlin-mode--token-text token) "@")
+ (kotlin-mode--try-forward-multi-annotation-bracket))
+
+ ;; Example: @Abc(def)
+ (t
+ (goto-char (1+ (kotlin-mode--token-start token)))
+ (kotlin-mode--try-forward-unescaped-annotation)))
+ (make-instance 'kotlin-mode--token
+ :type 'annotation
+ :text (buffer-substring-no-properties
+ (kotlin-mode--token-start token)
+ (point))
+ :start (kotlin-mode--token-start token)
+ :end (point)))
+ token))
+
+(defun kotlin-mode--try-forward-multi-annotation-bracket ()
+ "Move point forward to the end of square brackets.
+
+Return non-nil if succeeds. Keep position and return nil otherwise."
+ (let ((pos (point)))
+ (when (save-excursion
+ (forward-comment (point-max))
+ (eq (char-after) ?\[))
+ (condition-case nil
+ (progn
+ (forward-list)
+ t)
+ (scan-error
+ (goto-char pos)
+ nil)))))
+
+(defun kotlin-mode--try-forward-unescaped-annotation ()
+ "Move point forward to the end of unescapedAnnotation.
+
+Skip userType and constructor arguments if exists.
+
+Return non-nil if succeeds. Keep position and return nil otherwise."
+ (if (kotlin-mode--try-forward-type-reference)
+ (let ((pos (point)))
+ (when (save-excursion
+ ;; Line breaks are prohibited before bracket.
+ (kotlin-mode--forward-comments-but-not-line-breaks)
+ (eq (char-after) ?\())
+ (condition-case nil
+ (forward-list)
+ (scan-error
+ (goto-char pos))))
+ t)
+ nil))
+
+(defun kotlin-mode--backward-token ()
+ "Move point backward to the previous position of the end of a token.
+
+Return the token object. If no more tokens available, return a token with
+type `outside-of-buffer'."
+ (let ((pos (point)))
+ ;; Skip comments and whitespaces.
+ (let ((chunk (kotlin-mode--chunk-after)))
+ (when (kotlin-mode--chunk-comment-p chunk)
+ (goto-char (kotlin-mode--chunk-start chunk))))
+ (forward-comment (- (point)))
+ (cond
+ ;; Outside of buffer
+ ((bobp)
+ (make-instance 'kotlin-mode--token
+ :type 'outside-of-buffer
+ :text ""
+ :start (point)
+ :end (point)))
+
+ ;; Implicit semicolon
+ ((and
+ ;; Check (forward-comment (- (point))) skipped a newline.
+ (< (save-excursion
+ (kotlin-mode--goto-non-comment-eol)
+ (point))
+ pos)
+ (save-excursion (goto-char pos) (kotlin-mode--implicit-semi-p)))
+ (make-instance 'kotlin-mode--token
+ :type 'implicit-\;
+ :text (buffer-substring-no-properties (point) pos)
+ :start (point)
+ :end pos))
+
+ (t
+ (let ((token (kotlin-mode--extend-annotation-token-backward
+ (kotlin-mode--backward-token-simple))))
+ (cond
+ ;; Close parenthesis of "if ()", "while ()", or "for ()"
+ ((kotlin-mode--close-parenthesis-before-control-structure-body-p token)
+ (make-instance 'kotlin-mode--token
+ :type '\)-before-control-structure-body
+ :text (kotlin-mode--token-text token)
+ :start (kotlin-mode--token-start token)
+ :end (kotlin-mode--token-end token)))
+
+ ;; "else" not followed by "if" on the same line or "{"
+ ((kotlin-mode--bare-else-p token)
+ (make-instance 'kotlin-mode--token
+ :type 'bare-else
+ :text (kotlin-mode--token-text token)
+ :start (kotlin-mode--token-start token)
+ :end (kotlin-mode--token-end token)))
+
+ ;; super@label
+ ((save-excursion
+ (and (eq (kotlin-mode--token-type token) 'annotation)
+ (eq (char-before) ?>)
+ (kotlin-mode--try-backward-type-parameters)
+ (equal (kotlin-mode--token-text
+ (kotlin-mode--backward-token-simple))
+ "super")))
+ (kotlin-mode--try-backward-type-parameters)
+ (kotlin-mode--backward-token-simple)
+ (make-instance 'kotlin-mode--token
+ :type 'super
+ :text (buffer-substring-no-properties
+ (point)
+ (kotlin-mode--token-end token))
+ :start (point)
+ :end (kotlin-mode--token-end token)))
+
+ ;; "->" of lambda parameters
+ ((kotlin-mode--anonymous-function-parameter-arrow-p token)
+ (make-instance 'kotlin-mode--token
+ :type 'anonymous-function-parameter-arrow
+ :text (kotlin-mode--token-text token)
+ :start (kotlin-mode--token-start token)
+ :end (kotlin-mode--token-end token)))
+
+ ;; "->" of when expression
+ ((kotlin-mode--when-expression-arrow-p token)
+ (make-instance 'kotlin-mode--token
+ :type 'when-expression-arrow
+ :text (kotlin-mode--token-text token)
+ :start (kotlin-mode--token-start token)
+ :end (kotlin-mode--token-end token)))
+
+ (t token)))))))
+
+(defun kotlin-mode--backward-token-simple ()
+ "Like `kotlin-mode--backward-token' without recursion.
+
+This function does not return `implicit-;'"
+ (forward-comment (- (point)))
+ (cond
+ ;; Outside of buffer
+ ((bobp)
+ (make-instance 'kotlin-mode--token
+ :type 'outside-of-buffer
+ :text ""
+ :start (point)
+ :end (point)))
+
+ ;; Beginning of template expression
+ ((and (eq (char-before) ?\{)
+ (equal (get-text-property (1- (point)) 'syntax-table)
+ (string-to-syntax "|")))
+ (let ((pos-before-comment (point)))
+ (kotlin-mode--backward-string-chunk)
+ (make-instance
+ 'kotlin-mode--token
+ :type 'string-chunk-before-template-expression
+ :text (buffer-substring-no-properties (point) pos-before-comment)
+ :start (point)
+ :end pos-before-comment)))
+
+ ;; ::, ?:
+ ((member (buffer-substring-no-properties
+ (max (point-min) (- (point) 2))
+ (point))
+ '("::" "?:"))
+ (backward-char 2)
+ (make-instance 'kotlin-mode--token
+ :type 'operator
+ :text (buffer-substring-no-properties (point) (+ 2 (point)))
+ :start (point)
+ :end (+ 2 (point))))
+
+ ;; Separators and parentheses
+ ((memq (char-before) '(?, ?\; ?\{ ?\} ?\[ ?\] ?\( ?\) ?:))
+ (backward-char)
+ (make-instance 'kotlin-mode--token
+ :type (intern (string (char-after)))
+ :text (string (char-after))
+ :start (point)
+ :end (1+ (point))))
+
+ ;; Operator (3 letters)
+ ((member (buffer-substring-no-properties
+ (max (point-min) (- (point) 3))
+ (point))
+ '("===" "!==" "!is" "!in" "..<"))
+ (backward-char 3)
+ (make-instance 'kotlin-mode--token
+ :type 'operator
+ :text (buffer-substring-no-properties (point) (+ 3 (point)))
+ :start (point)
+ :end (+ 3 (point))))
+
+ ;; Operator (2 letters, other than as, in, or is)
+ ((member (buffer-substring-no-properties
+ (max (point-min) (- (point) 2))
+ (point))
+ '("++" "--"
+ "&&" "||"
+ "+=" "-=" "*=" "/=" "%="
+ ".." "?."
+ "<=" ">=" "!=" "=="
+ "->"))
+ (backward-char 2)
+ (make-instance 'kotlin-mode--token
+ :type 'operator
+ :text (buffer-substring-no-properties (point) (+ 2 (point)))
+ :start (point)
+ :end (+ 2 (point))))
+
+ ;; Open angle bracket for type parameters
+ ;;
+ ;; We use a heuristic: spaces are inserted around inequality sign,
+ ;; but not for angle bracket, and a type parameter starts with an
+ ;; upper case characters, parenthesis, or keyword 'reified',
+ ;; keyword 'in', keyword 'out', keyword 'suspend', or annotations.
+ ((and (eq (char-before) ?<)
+ (looking-at
+ (rx (or (any "_(@[*" upper)
+ (seq (or "reified" "in" "out")
+ word-end)))))
+ (backward-char)
+ (make-instance 'kotlin-mode--token
+ :type '<
+ :text "<"
+ :start (point)
+ :end (1+ (point))))
+
+ ;; Close angle bracket for type parameters
+ ;;
+ ;; Close angle bracket follows identifier, parentheses, question symbols,
+ ;; or other angle brackets (e.g. Foo>)
+ ((and (eq (char-before) ?>)
+ (save-excursion
+ (skip-chars-backward "?)>")
+ (skip-syntax-backward "w")
+ (looking-at "[[:upper:]_]")))
+ (backward-char)
+ (make-instance 'kotlin-mode--token
+ :type '>
+ :text ">"
+ :start (point)
+ :end (1+ (point))))
+
+ ;; ? or as?
+ ((eq (char-before) ??)
+ (let ((pos-before-comment (point)))
+ (backward-char)
+ (skip-syntax-backward "w")
+ (unless (looking-at (rx "as?"))
+ (goto-char pos-before-comment)
+ (backward-char))
+ (make-instance
+ 'kotlin-mode--token
+ :type 'operator
+ :text (buffer-substring-no-properties (point) pos-before-comment)
+ :start (point)
+ :end pos-before-comment)))
+
+ ;; Operator (1 letter)
+ ((member (buffer-substring-no-properties
+ (max (point-min) (- (point) 1))
+ (point))
+ '("*" "%" "/" "+" "-" "!" "=" "." "?" "<" ">" "&"))
+ (backward-char)
+ (make-instance 'kotlin-mode--token
+ :type 'operator
+ :text (buffer-substring-no-properties (point) (1+ (point)))
+ :start (point)
+ :end (1+ (point))))
+
+ ;; Backquoted identifier
+ ((eq (char-before) ?`)
+ (let ((pos-before-comment (point)))
+ (kotlin-mode--backward-string-chunk)
+ (when (eq (char-before) ?@)
+ (backward-char))
+ (make-instance
+ 'kotlin-mode--token
+ :type (if (eq (char-after) ?@) 'annotation 'atom)
+ :text (buffer-substring-no-properties (point) pos-before-comment)
+ :start (point)
+ :end pos-before-comment)))
+
+ ;; character literal
+ ((eq (char-before) ?')
+ (let ((pos-before-comment (point)))
+ (kotlin-mode--backward-string-chunk)
+ (make-instance
+ 'kotlin-mode--token
+ :type 'atom
+ :text (buffer-substring-no-properties (point) pos-before-comment)
+ :start (point)
+ :end pos-before-comment)))
+
+ ;; String
+ ((eq (char-before) ?\")
+ (let ((pos-before-comment (point)))
+ (backward-char)
+ (kotlin-mode--beginning-of-string)
+ (make-instance
+ 'kotlin-mode--token
+ :type 'atom
+ :text (buffer-substring-no-properties (point) pos-before-comment)
+ :start (point)
+ :end pos-before-comment)))
+
+ ;; Other tokens including identifiers, keywords, labels, numbers,
+ ;; and annotations without bracket.
+ ;;
+ ;; Note that we parse 123.456e+2 as "123" "." "456e" "+" "2".
+ (t
+ (let* ((pos-before-comment (point))
+ (text
+ (cond
+ ;; Labels (and single at sign)
+ ((eq (char-before) ?@)
+ (backward-char)
+ (skip-syntax-backward "w")
+ (buffer-substring-no-properties (point) pos-before-comment))
+
+ ;; Identifiers, keywords, numbers, (part of) annotations
+ ((eq (syntax-class (syntax-after (1- (point))))
+ (syntax-class (string-to-syntax "w")))
+ (skip-syntax-backward "w")
+ (when (eq (char-before) ?@)
+ ;; Annotation, return@ or something like it.
+ (backward-char)
+ (let ((pos (point)))
+ (skip-syntax-backward "w")
+ (unless (looking-at
+ (rx (or "return"
+ "continue"
+ "break"
+ "this"
+ "super")))
+ (goto-char pos))))
+ (buffer-substring-no-properties (point) pos-before-comment))
+
+ ;; Unknown character type. Treats as a single-letter token.
+ (t (backward-char) (string (char-after)))))
+ (type (cond
+ ((member text '("as" "in" "is"))
+ 'operator)
+
+ ((string-prefix-p "@" text)
+ ;; Strictly speaking, single at sign is not an
+ ;; annotation, but we treat it as an annotation.
+ 'annotation)
+
+ ((string-match "^return\\>" text)
+ 'return)
+
+ ((string-match "^continue\\>" text)
+ 'return)
+
+ ((string-match "^break\\>" text)
+ 'break)
+
+ ((string-match "^this\\>" text)
+ 'this)
+
+ ((string-match "^super\\>" text)
+ 'super)
+
+ ((eq (aref text (1- (length text))) ?@)
+ 'label)
+
+ (t
+ 'atom))))
+ (make-instance 'kotlin-mode--token
+ :type type
+ :text text
+ :start (point)
+ :end (+ (point) (length text)))))))
+
+(defun kotlin-mode--extend-annotation-token-backward (token)
+ "Return annotation token if TOKEN is the end of an annotation.
+
+TOKEN is assumed to be a return value of `kotlin-mode--backward-token-simple'."
+ (let ((pos (point)))
+ (goto-char (kotlin-mode--token-end token))
+ (cond
+ ;; Example: @[Abc Def Ghi]
+ ;; Example: @file:[Abc Def Ghi]
+ ((eq (char-before) ?\])
+ (condition-case nil
+ (progn
+ (backward-list)
+ (forward-comment (- (point)))
+ (if (or (and (eq (char-before) ?@) (prog1 t (backward-char)))
+ (kotlin-mode--try-backward-annotation-use-site-target))
+ (make-instance 'kotlin-mode--token
+ :type 'annotation
+ :text (buffer-substring-no-properties
+ (point)
+ (kotlin-mode--token-end token))
+ :start (point)
+ :end (kotlin-mode--token-end token))
+ ;; Not an annotation.
+ (goto-char pos)
+ token))
+ (scan-error
+ ;; Not an annotation.
+ (goto-char pos)
+ token)))
+
+ ;; Example: @Abc(def)
+ ;; Example: @file:Abc(def)
+ ;; Example: @Abc
+ ;; Example: @file:Abc
+ ;; Example: @Abc
+ ((or (memq (char-before) '(?\) ?` ?>))
+ (eq (syntax-class (syntax-after (1- (point))))
+ (syntax-class (string-to-syntax "w"))))
+ (when (eq (char-before) ?\))
+ (condition-case nil
+ (progn
+ (backward-list)
+ ;; Line breaks are prohibited before bracket.
+ (kotlin-mode--backward-comments-but-not-line-breaks)
+ (unless (or (memq (char-before) '(?` ?>))
+ (eq (syntax-class (syntax-after (1- (point))))
+ (syntax-class (string-to-syntax "w"))))
+ (goto-char pos)))
+ (scan-error
+ (goto-char pos))))
+ (if (and
+ (kotlin-mode--try-backward-type-reference)
+ (or (and (eq (char-before) ?@) (prog1 t (backward-char)))
+ (kotlin-mode--try-backward-annotation-use-site-target)))
+ (make-instance 'kotlin-mode--token
+ :type 'annotation
+ :text (buffer-substring-no-properties
+ (point)
+ (kotlin-mode--token-end token))
+ :start (point)
+ :end (kotlin-mode--token-end token))
+ ;; Not an annotation.
+ (goto-char pos)
+ token))
+
+ ;; Not an annotation.
+ (t
+ (goto-char pos)
+ token))))
+
+(defun kotlin-mode--try-backward-annotation-use-site-target ()
+ "Try backward annotationUseSiteTarget.
+
+Try to skip colon, word, and at sign.
+
+Return non-nil if succeeds. Keep position and return nil otherwise."
+ (let ((pos (point)))
+ (forward-comment (- (point)))
+ (if (eq (char-before) ?:)
+ (progn
+ (backward-char)
+ (forward-comment (- (point)))
+ (if (and (< (skip-syntax-backward "w") 0)
+ (eq (char-before) ?@))
+ (progn
+ (backward-char)
+ t)
+ (goto-char pos)
+ nil))
+ (goto-char pos)
+ nil)))
+
+(defun kotlin-mode--forward-string-chunk ()
+ "Skip forward a string chunk.
+
+A string chunk is a part of single-line/multiline string delimited with
+quotation marks or template expressions."
+ (condition-case nil
+ (goto-char (scan-sexps (point) 1))
+ (scan-error (goto-char (point-max)))))
+
+(defun kotlin-mode--backward-string-chunk ()
+ "Skip backward a string chunk.
+
+A string chunk is a part of single-line/multiline string delimited with
+quotation marks or template expressions."
+ (condition-case nil
+ (goto-char (scan-sexps (point) -1))
+ (scan-error (goto-char (point-min)))))
+
+(defun kotlin-mode--beginning-of-string ()
+ "Move point to the beginning of single-line/multiline string.
+
+Return the point of the beginning.
+
+Assuming the point is on a string."
+ (goto-char (or (nth 8 (syntax-ppss)) (point)))
+ (let (matching-bracket)
+ (while (and
+ (setq matching-bracket
+ (get-text-property
+ (point)
+ 'kotlin-property--matching-bracket))
+ (< (point-min) matching-bracket))
+ (goto-char matching-bracket)
+ (goto-char (nth 8 (syntax-ppss))))
+ (point)))
+
+(defun kotlin-mode--end-of-string ()
+ "Move point to the end of single-line/multiline string.
+
+Assuming the point is on a string."
+ (goto-char (or (nth 8 (syntax-ppss)) (point)))
+ (let (matching-bracket)
+ (kotlin-mode--forward-string-chunk)
+ (while (and (setq matching-bracket
+ (get-text-property
+ (1- (point))
+ 'kotlin-property--matching-bracket))
+ (< matching-bracket (point-max)))
+ (goto-char matching-bracket)
+ (kotlin-mode--forward-string-chunk)))
+ (point))
+
+(defun kotlin-mode--goto-non-comment-bol ()
+ "Back to the beginning of line that is not inside a comment."
+ (beginning-of-line)
+ (let (chunk)
+ (while (progn
+ (setq chunk (kotlin-mode--chunk-after))
+ (kotlin-mode--chunk-comment-p chunk))
+ ;; The point is in a comment. Backs to the beginning of the comment.
+ (goto-char (kotlin-mode--chunk-start chunk))
+ (beginning-of-line))))
+
+(defun kotlin-mode--goto-non-comment-eol ()
+ "Proceed to the end of line that is not inside a comment.
+
+If this line ends with a single-line comment, goto just before the comment."
+ (end-of-line)
+ (let (chunk)
+ (while (progn
+ (setq chunk (kotlin-mode--chunk-after))
+ (kotlin-mode--chunk-comment-p chunk))
+ ;; The point is in a comment.
+ (if (kotlin-mode--chunk-single-line-comment-p chunk)
+ ;; This is a single-line comment
+ ;; Back to the beginning of the comment.
+ (goto-char (kotlin-mode--chunk-start chunk))
+ ;; This is a multiline comment
+ ;; Proceed to the end of the comment.
+ (goto-char (kotlin-mode--chunk-start chunk))
+ (forward-comment 1)
+ (end-of-line)
+ ;; If the comment is incomplete, back to the beginning of the comment.
+ (when (and (eobp) (kotlin-mode--chunk-after))
+ (goto-char (kotlin-mode--chunk-start (kotlin-mode--chunk-after))))))))
+
+(defun kotlin-mode--bol-other-than-comments-p ()
+ "Return t if there is nothing other than comments in the front of this line.
+
+Return nil otherwise.
+Newlines inside comments are ignored."
+ ;; Foo // ← bol
+ ;; /* */ Foo // ← bol
+ ;; X /* */ Foo // ← not bol
+ ;;
+ ;; /*
+ ;; */ /* */ /*
+ ;; */ Foo // ← bol
+ ;;
+ ;; X /*
+ ;; */ /* */ /*
+ ;; */ Foo // ← not bol
+ ;;
+ ;; X
+ ;; /* */ /*
+ ;; */ Foo // ← bol
+ (save-excursion
+ (let ((pos (point)))
+ (kotlin-mode--goto-non-comment-bol)
+ (forward-comment (point-max))
+ (<= pos (point)))))
+
+(defun kotlin-mode--eol-other-than-comments-p ()
+ "Return t if there is nothing other than comments until the end of this line.
+
+Return nil otherwise.
+Newlines inside comments are ignored."
+ (save-excursion
+ (let ((pos (point)))
+ (kotlin-mode--goto-non-comment-eol)
+ (forward-comment (- (point)))
+ (<= (point) pos))))
+
+(defun kotlin-mode--skip-whitespaces ()
+ "Skip forward whitespaces and newlines."
+ (skip-syntax-forward " >"))
+
+(defun kotlin-mode--forward-comments-but-not-line-breaks ()
+ "Skip forward comments but not line breaks."
+ (goto-char (min
+ (save-excursion
+ (kotlin-mode--goto-non-comment-eol)
+ (point))
+ (save-excursion
+ (forward-comment (point-max))
+ (point)))))
+
+(defun kotlin-mode--backward-comments-but-not-line-breaks ()
+ "Skip backward comments but not line breaks."
+ (goto-char (max
+ (save-excursion
+ (kotlin-mode--goto-non-comment-bol)
+ (point))
+ (save-excursion
+ (forward-comment (- (point)))
+ (point)))))
+
(provide 'kotlin-mode-lexer)
;;; kotlin-mode-lexer.el ends here
diff --git a/kotlin-mode.el b/kotlin-mode.el
index 6b715a5..eedbb35 100644
--- a/kotlin-mode.el
+++ b/kotlin-mode.el
@@ -1,11 +1,12 @@
;;; kotlin-mode.el --- Major mode for kotlin -*- lexical-binding: t; -*-
-;; Copyright © 2015 Shodai Yokoyama
+;; Copyright © 2015 Shodai Yokoyama
;; Author: Shodai Yokoyama (quantumcars@gmail.com)
;; Version: 2.0.0
;; Keywords: languages
;; Package-Requires: ((emacs "24.3"))
+;; URL: https://github.com/Emacs-Kotlin-Mode-Maintainers/kotlin-mode
;; 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
@@ -22,7 +23,7 @@
;;; Commentary:
-;;
+;; Major-mode for Kotlin programming language.
;;; Code:
@@ -33,6 +34,7 @@
(require 'eieio)
(require 'kotlin-mode-lexer)
+(require 'kotlin-mode-indent)
(defgroup kotlin nil
"A Kotlin major mode."
@@ -59,18 +61,25 @@
:type 'string
:group 'kotlin)
+;; REPL
+
(defun kotlin-do-and-repl-focus (f &rest args)
+ "Call function F with ARGS and pop to the REPL buffer."
(apply f args)
(pop-to-buffer kotlin-repl-buffer))
(defun kotlin-send-region (start end)
- "Send current region to Kotlin interpreter."
+ "Send current region to Kotlin interpreter.
+
+START and END define region within current buffer."
(interactive "r")
(comint-send-region kotlin-repl-buffer start end)
(comint-send-string kotlin-repl-buffer "\n"))
(defun kotlin-send-region-and-focus (start end)
- "Send current region to Kotlin interpreter and switch to it."
+ "Send current region to Kotlin interpreter and switch to it.
+
+START and END define region within current buffer."
(interactive "r")
(kotlin-do-and-repl-focus 'kotlin-send-region start end))
@@ -97,6 +106,7 @@
(kotlin-do-and-repl-focus 'kotlin-send-block))
(defun kotlin-send-line ()
+ "Send current line to Kotlin interpreter."
(interactive)
(kotlin-send-region
(line-beginning-position)
@@ -121,11 +131,13 @@
kotlin-args-repl))
(set (make-local-variable 'comint-preoutput-filter-functions)
- (cons (lambda (string)
- (replace-regexp-in-string "\x1b\\[.[GJK]" "" string)) nil)))
+ (list (lambda (string)
+ (replace-regexp-in-string "\x1b\\[.[GJK]" "" string)))))
(pop-to-buffer kotlin-repl-buffer))
+;; Keymap
+
(defvar kotlin-mode-map
(let ((map (make-keymap)))
(define-key map (kbd "C-c C-z") 'kotlin-repl)
@@ -133,37 +145,18 @@
(define-key map (kbd "C-c C-r") 'kotlin-send-region)
(define-key map (kbd "C-c C-c") 'kotlin-send-block)
(define-key map (kbd "C-c C-b") 'kotlin-send-buffer)
+ (define-key map [remap indent-new-comment-line]
+ #'kotlin-mode--indent-new-comment-line)
map)
- "Keymap for kotlin-mode")
-
-(defvar kotlin-mode-syntax-table
- (let ((st (make-syntax-table)))
-
- ;; Strings
- (modify-syntax-entry ?\" "\"" st)
- (modify-syntax-entry ?\' "\"" st)
- (modify-syntax-entry ?` "\"" st)
-
- ;; `_' and `@' as being a valid part of a symbol
- (modify-syntax-entry ?_ "_" st)
- (modify-syntax-entry ?@ "_" st)
-
- ;; b-style comment
- (modify-syntax-entry ?/ ". 124b" st)
- (modify-syntax-entry ?* ". 23n" st)
- (modify-syntax-entry ?\n "> b" st)
- (modify-syntax-entry ?\r "> b" st)
- st))
-
-(defconst kotlin-mode--closing-brackets '(?} ?\) ?\]))
+ "Keymap for `kotlin-mode'.")
;;; Font Lock
-(defconst kotlin-mode--misc-keywords
+(defconst kotlin-mode--package-keywords
'("package" "import"))
(defconst kotlin-mode--type-decl-keywords
- '("sealed" "inner" "data" "class" "interface" "trait" "typealias" "enum" "object"))
+ '("class" "interface" "typealias" "object"))
(defconst kotlin-mode--fun-decl-keywords
'("fun"))
@@ -187,8 +180,8 @@
(defconst kotlin-mode--generic-type-parameter-keywords
'("where"))
-(defvar kotlin-mode--keywords
- (append kotlin-mode--misc-keywords
+(defvar kotlin-mode--misc-keywords
+ (append kotlin-mode--package-keywords
kotlin-mode--type-decl-keywords
kotlin-mode--fun-decl-keywords
kotlin-mode--val-decl-keywords
@@ -200,15 +193,14 @@
(defconst kotlin-mode--constants-keywords
'("null" "true" "false"))
-(defconst kotlin-mode--modifier-keywords
- '("open" "private" "protected" "public" "lateinit"
- "override" "abstract" "final" "companion"
- "annotation" "internal" "const" "in" "out"
- "actual" "expect" "crossinline" "inline" "noinline" "external"
- "infix" "operator" "reified" "suspend" "tailrec" "vararg"))
+(defconst kotlin-mode--variance-modifier-keywords
+ '("in" "out"))
+
+(defconst kotlin-mode--reification-modifier-keywords
+ '("reified"))
(defconst kotlin-mode--property-keywords
- '("by" "get" "set")) ;; "by" "get" "set"
+ '("by" "get" "set"))
(defconst kotlin-mode--initializer-keywords
'("init" "constructor"))
@@ -223,13 +215,13 @@
(defvar kotlin-mode--font-lock-keywords
`(;; Keywords
(,(rx-to-string
- `(and bow (group (or ,@kotlin-mode--keywords)) eow)
+ `(and bow (group (or ,@kotlin-mode--misc-keywords)) eow)
t)
1 font-lock-keyword-face)
;; Package names
(,(rx-to-string
- `(and (or ,@kotlin-mode--misc-keywords) (+ space)
+ `(and (or ,@kotlin-mode--package-keywords) (+ space)
(group (+ (any word ?.))))
t)
1 font-lock-string-face)
@@ -273,11 +265,14 @@
t)
1 font-lock-function-name-face)
- ;; Access modifier
- ;; Access modifier is valid identifier being used as variable
+ ;; Modifiers
+ ;; Modifier is valid identifier being used as variable
;; TODO: Highlight only modifiers in the front of class/fun
(,(rx-to-string
- `(and bow (group (or ,@kotlin-mode--modifier-keywords))
+ `(and bow (group (or ,@kotlin-mode--modifier-keywords
+ ,@kotlin-mode--companion-modifier-keywords
+ ,@kotlin-mode--variance-modifier-keywords
+ ,@kotlin-mode--reification-modifier-keywords))
eow)
t)
1 font-lock-keyword-face)
@@ -312,9 +307,14 @@
;; String interpolation
(kotlin-mode--match-interpolation 0 font-lock-variable-name-face t))
- "Default highlighting expression for `kotlin-mode'")
+ "Default highlighting expression for `kotlin-mode'.")
(defun kotlin-mode--match-interpolation (limit)
+ "Find template expression before LIMIT.
+
+Template expressions must be propertized by `kotlin-mode--syntax-propertize'.
+If a template expression is found, move to that point, set `match-data',
+and return non-nil. Return nil otherwise."
(let ((pos (next-single-char-property-change
(point)
'kotlin-property--interpolation
@@ -328,233 +328,56 @@
t)
(kotlin-mode--match-interpolation limit))))))
-(defun kotlin-mode--prev-line ()
- "Moves up to the nearest non-empty line"
- (if (not (bobp))
- (progn
- (forward-line -1)
- (while (and (looking-at "^[ \t]*$") (not (bobp)))
- (forward-line -1)))))
-
-(defun kotlin-mode--prev-line-begins (pattern)
- "Return whether the previous line begins with the given pattern"
- (save-excursion
- (kotlin-mode--prev-line)
- (looking-at (format "^[ \t]*%s" pattern))))
-
-(defun kotlin-mode--prev-line-ends (pattern)
- "Return whether the previous line ends with the given pattern"
- (save-excursion
- (kotlin-mode--prev-line)
- (looking-at (format ".*%s[ \t]*$" pattern))))
-
-(defun kotlin-mode--line-begins (pattern)
- "Return whether the current line begins with the given pattern"
- (save-excursion
- (beginning-of-line)
- (looking-at (format "^[ \t]*%s" pattern))))
-
-(defun kotlin-mode--line-ends (pattern)
- "Return whether the current line ends with the given pattern"
- (save-excursion
- (beginning-of-line)
- (looking-at (format ".*%s[ \t]*$" pattern))))
-
-(defun kotlin-mode--line-contains (pattern)
- "Return whether the current line contains the given pattern"
- (save-excursion
- (beginning-of-line)
- (looking-at (format ".*%s.*" pattern))))
-
-(defun kotlin-mode--line-continuation()
- "Return whether this line continues a statement in the previous line"
- (or
- (and (kotlin-mode--prev-line-begins "\\(if\\|for\\|while\\)[ \t]+(")
- (kotlin-mode--prev-line-ends ")[[:space:]]*\\(\/\/.*\\|\\/\\*.*\\)?"))
- (and (kotlin-mode--prev-line-begins "else[ \t]*")
- (not (kotlin-mode--prev-line-begins "else [ \t]*->"))
- (not (kotlin-mode--prev-line-ends "{.*")))
- (or
- (kotlin-mode--line-begins "\\([.=:]\\|->\\|\\(\\(private\\|public\\|protected\\|internal\\)[ \t]*\\)?[sg]et\\b\\)"))))
-
-(defun kotlin-mode--in-comment-block ()
- "Return whether the cursor is within a standard comment block structure
- of the following format:
- /**
- * Description here
- */"
- (save-excursion
- (let ((in-comment-block nil)
- (keep-going (and
- (not (kotlin-mode--line-begins "\\*\\*+/"))
- (not (kotlin-mode--line-begins "/\\*"))
- (nth 4 (syntax-ppss)))))
- (while keep-going
- (kotlin-mode--prev-line)
- (cond
- ((kotlin-mode--line-begins "/\\*")
- (setq keep-going nil)
- (setq in-comment-block t))
- ((bobp)
- (setq keep-going nil))
- ((kotlin-mode--line-contains "\\*/")
- (setq keep-going nil))))
- in-comment-block)))
-
-(defun kotlin-mode--first-line-p ()
- "Determine if point is on the first line."
- (save-excursion
- (beginning-of-line)
- (bobp)
- )
- )
-
-(defun kotlin-mode--line-closes-block-p ()
- "Return whether or not the start of the line closes its containing block."
- (save-excursion
- (back-to-indentation)
- (memq (following-char) kotlin-mode--closing-brackets)
- ))
-
-(defun kotlin-mode--get-opening-char-indentation (parser-state-index)
- "Determine the indentation of the line that starts the current block.
-
-Caller must pass in PARSER-STATE-INDEX, which refers to the index
-of the list returned by `syntax-ppss'.
-
-If it does not exist, will return nil."
- (save-excursion
- (back-to-indentation)
- (let ((opening-pos (nth parser-state-index (syntax-ppss))))
- (when opening-pos
- (goto-char opening-pos)
- (current-indentation)))
- )
- )
-
-(defun kotlin-mode--indent-for-continuation ()
- "Return the expected indentation for a continuation."
- (kotlin-mode--prev-line)
- (if (kotlin-mode--line-continuation)
- (kotlin-mode--indent-for-continuation)
- (+ kotlin-tab-width (current-indentation)))
- )
-
-(defun kotlin-mode--indent-for-code ()
- "Return the level that this line of code should be indented to."
- (let ((indent-opening-block (kotlin-mode--get-opening-char-indentation 1)))
- (cond
- ((kotlin-mode--line-continuation) (save-excursion (kotlin-mode--indent-for-continuation)))
- ((booleanp indent-opening-block) 0)
- ((kotlin-mode--line-closes-block-p) indent-opening-block)
- (t (+ indent-opening-block kotlin-tab-width)))
- ))
-
-(defun kotlin-mode--indent-for-comment ()
- "Return the level that this line of comment should be indented to."
- (let ((opening-indentation (kotlin-mode--get-opening-char-indentation 8)))
- (if opening-indentation
- (1+ opening-indentation)
- 0)
- ))
-
-(defun kotlin-mode--indent-line ()
- "Indent the current line of Kotlin code."
- (interactive)
- (let ((follow-indentation-p
- (and (<= (line-beginning-position) (point))
- (>= (+ (line-beginning-position)
- (current-indentation))
- (point)))))
- (save-excursion
- (beginning-of-line)
- (if (bobp) ; 1.)
- (progn
- (kotlin-mode--beginning-of-buffer-indent))
- (let ((not-indented t) cur-indent)
- (cond ((looking-at "^[ \t]*\\.") ; line starts with .
- (save-excursion
- (kotlin-mode--prev-line)
- (cond ((looking-at "^[ \t]*\\.")
- (setq cur-indent (current-indentation)))
-
- (t
- (setq cur-indent (+ (current-indentation) (* 2 kotlin-tab-width)))))
- (if (< cur-indent 0)
- (setq cur-indent 0))))
-
- ((looking-at "^[ \t]*}") ; line starts with }
- (save-excursion
- (kotlin-mode--prev-line)
- (while (and (or (looking-at "^[ \t]*$") (looking-at "^[ \t]*\\.")) (not (bobp)))
- (kotlin-mode--prev-line))
- (cond ((or (looking-at ".*{[ \t]*$") (looking-at ".*{.*->[ \t]*$"))
- (setq cur-indent (current-indentation)))
- (t
- (setq cur-indent (- (current-indentation) kotlin-tab-width)))))
- (if (< cur-indent 0)
- (setq cur-indent 0)))
-
- ((looking-at "^[ \t]*)") ; line starts with )
- (save-excursion
- (kotlin-mode--prev-line)
- (setq cur-indent (- (current-indentation) kotlin-tab-width)))
- (if (< cur-indent 0)
- (setq cur-indent 0)))
-
- (t
- (save-excursion
- (while not-indented
- (kotlin-mode--prev-line)
- (cond ((looking-at ".*{[ \t]*$") ; line ends with {
- (setq cur-indent (+ (current-indentation) kotlin-tab-width))
- (setq not-indented nil))
-
- ((looking-at "^[ \t]*}") ; line starts with }
- (setq cur-indent (current-indentation))
- (setq not-indented nil))
-
- ((looking-at ".*{.*->[ \t]*$") ; line ends with ->
- (setq cur-indent (+ (current-indentation) kotlin-tab-width))
- (setq not-indented nil))
-
- ((looking-at ".*([ \t]*$") ; line ends with (
- (setq cur-indent (+ (current-indentation) kotlin-tab-width))
- (setq not-indented nil))
-
- ((looking-at "^[ \t]*).*$") ; line starts with )
- (setq cur-indent (current-indentation))
- (setq not-indented nil))
-
- ((bobp) ; 5.)
- (setq not-indented nil)))))))
- (if cur-indent
- (indent-line-to cur-indent)
- (indent-line-to 0)))))
-
- (when follow-indentation-p
- (back-to-indentation))))
-
-
-(defun kotlin-mode--beginning-of-buffer-indent ()
- (indent-line-to 0))
+
+;; the Kotlin mode
;;;###autoload
(define-derived-mode kotlin-mode prog-mode "Kotlin"
"Major mode for editing Kotlin."
(setq font-lock-defaults '((kotlin-mode--font-lock-keywords) nil nil))
+
(setq-local parse-sexp-lookup-properties t)
(add-hook 'syntax-propertize-extend-region-functions
#'kotlin-mode--syntax-propertize-extend-region
nil t)
(setq-local syntax-propertize-function #'kotlin-mode--syntax-propertize)
- (set (make-local-variable 'comment-start) "//")
- (set (make-local-variable 'comment-padding) 1)
- (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
- (set (make-local-variable 'comment-end) "")
- (set (make-local-variable 'indent-line-function) 'kotlin-mode--indent-line)
- (setq-local adaptive-fill-regexp comment-start-skip)
+
+ (setq-local comment-start "//")
+ (setq-local comment-end "")
+ (setq-local comment-padding 1)
+ (setq-local comment-start-skip
+ (rx (seq (zero-or-more (syntax whitespace))
+ (or
+ ;; Single-line comment
+ (seq "/" (one-or-more "/"))
+ ;; Multi-line comment
+ (seq "/" (one-or-more "*"))
+ ;; Middle of multi-line-comment
+ (seq (one-or-more "*") " "))
+ (zero-or-more (syntax whitespace)))))
+ (setq-local adaptive-fill-regexp
+ (rx (seq (zero-or-more (syntax whitespace))
+ (or
+ ;; Single-line comment
+ (seq "/" (one-or-more "/"))
+ ;; Middle of multi-line-comment
+ (seq (one-or-more "*") " "))
+ (zero-or-more (syntax whitespace)))))
+ (setq-local fill-indent-according-to-mode t)
+ (setq-local comment-multi-line t)
+
+ (setq-local indent-line-function 'kotlin-mode--indent-line)
+
+ (setq-local electric-indent-chars
+ (append "{}()[]:;,." electric-indent-chars))
+
+ (add-hook 'post-self-insert-hook #'kotlin-mode--post-self-insert nil t)
+
+ (setq-local kotlin-mode--anchor-overlay
+ (make-overlay (point-min) (point-min) nil t))
+
+ (delete-overlay kotlin-mode--anchor-overlay)
:group 'kotlin
:syntax-table kotlin-mode-syntax-table)
diff --git a/test/kotlin-mode-test.el b/test/kotlin-mode-test.el
index 9749006..b6f1ebf 100644
--- a/test/kotlin-mode-test.el
+++ b/test/kotlin-mode-test.el
@@ -76,7 +76,7 @@ println(arg)
(should (equal (buffer-string) "fun test(args: Array) {
args.forEach(arg ->
- println(arg)
+ println(arg)
)
"))
)))
@@ -180,26 +180,94 @@ println(arg)
(while (and (looking-at "^[ \t]*$") (not (eobp)))
(forward-line)))
-(ert-deftest kotlin-mode--sample-test ()
- (with-temp-buffer
- (insert-file-contents "test/sample.kt")
- (goto-char (point-min))
- (kotlin-mode)
- (setq-local indent-tabs-mode nil)
- (setq-local tab-width 4)
- (setq-local kotlin-tab-width 4)
- (while (not (eobp))
- (let ((expected-line (thing-at-point 'line)))
+(defvar kotlin-mode--test-keep-going nil
+ "If non-nil, do not stop at error in `kotlin-mode--sample-test'.")
- ;; Remove existing indentation
- (beginning-of-line)
- (delete-region (point) (progn (skip-chars-forward " \t") (point)))
+(ert-deftest kotlin-mode--sample-test ()
+ (dolist (filename '("test/sample.kt" "test/pathological.kt"))
+ (with-temp-buffer
+ (insert-file-contents filename)
+ (goto-char (point-min))
+ (kotlin-mode)
+ (setq-local indent-tabs-mode nil)
+ (setq-local tab-width 4)
+ (setq-local kotlin-tab-width 4)
- ;; Indent the line
- (kotlin-mode--indent-line)
+ (while (not (eobp))
+ (kotlin-mode--test-current-line filename nil)
- ;; Check that the correct indentation is re-applied
- (should (equal expected-line (thing-at-point 'line)))
+ ;; Indent without following lines
+ (narrow-to-region (point-min) (line-end-position))
+ (kotlin-mode--test-current-line filename t)
+ (widen)
;; Go to the next non-empty line
(next-non-empty-line)))))
+
+(defun kotlin-mode--test-current-line (filename truncated)
+ (back-to-indentation)
+ (let (;; (thing-at-point 'line) returns string with property, so
+ ;; we use `buffer-substring-no-properties'.
+ (expected-line (buffer-substring-no-properties
+ (line-beginning-position)
+ (line-end-position)))
+ actual-line
+ (original-indent (current-column))
+ ;; Known bug when the following lines are not present.
+ (known-bug-when-truncated
+ (looking-at ".*//.*KNOWN_BUG_WHEN_TRUNCATED"))
+ (known-bug (looking-at ".*//.*KNOWN_BUG\\($\\| \\)")))
+ ;; Remove existing indentation, or indent to column 1 if expected
+ ;; indentation is column 0.
+ ;; Keep indent inside multiline string.
+ (unless (kotlin-mode--chunk-multiline-string-p
+ (kotlin-mode--chunk-after))
+ (if (= original-indent 0)
+ (indent-line-to 1)
+ (delete-horizontal-space)))
+
+ ;; Indent the line
+ (kotlin-mode--indent-line)
+
+ (setq actual-line (buffer-substring-no-properties
+ (line-beginning-position)
+ (line-end-position)))
+
+ ;; Check that the correct indentation is re-applied
+ (unless (and truncated known-bug)
+ (if (or known-bug (and truncated known-bug-when-truncated))
+ (if (equal expected-line actual-line)
+ (message "%s:%s:info: %s is fixed somehow"
+ filename
+ (line-number-at-pos)
+ (if truncated "KNOWN_BUG_WHEN_TRUNCATED" "KNOWN_BUG"))
+ (back-to-indentation)
+ (message "%s:%s:warn: (known bug) %sexpected indentation to column %d but %d"
+ filename
+ (line-number-at-pos)
+ (if truncated "(when code is truncated) " "")
+ original-indent
+ (current-column)))
+ (if (and kotlin-mode--test-keep-going
+ (not (equal expected-line actual-line)))
+ (message "%s:%s:err: %sexpected indentation to column %d but %d"
+ filename
+ (line-number-at-pos)
+ (if truncated "(when code is truncated) " "")
+ original-indent
+ (current-column))
+ (should
+ (equal
+ (format "%s:%s: %s%s"
+ filename
+ (line-number-at-pos)
+ (if truncated "(when code is truncated) " "")
+ expected-line)
+ (format "%s:%s: %s%s"
+ filename
+ (line-number-at-pos)
+ (if truncated "(when code is truncated) " "")
+ actual-line))))))
+
+ ;; Restore to original indentation for KNOWN_BUG line.
+ (indent-line-to original-indent)))
diff --git a/test/pathological.kt b/test/pathological.kt
new file mode 100644
index 0000000..2c2624a
--- /dev/null
+++ b/test/pathological.kt
@@ -0,0 +1,2150 @@
+#!/usr/bin/kotlinc -script
+
+// fileAnnotation
+@file
+ :
+ [
+ A(a = 1) // No Comma here
+ B
+ .
+ C
+ .
+ D // No Comma here
+ E
+ ]
+
+@file
+ :
+ Foo
+ .
+ Bar<
+ A // KNOWN_BUG
+ > // KNOWN_BUG
+ .
+ Baz
+
+@file
+ :
+ Foo(aaa)
+
+// packageHeader
+package foo // Line breaks are prohibited after "package".
+ .bar // Line breaks are prohibited after dot.
+ .baz
+
+// importList, importHeader
+import a // Line breaks are prohibited after "import".
+ .b // Line breaks are prohibited after dot.
+ .c
+ .*
+
+// importAlias
+import a
+ . b
+ . c
+ as d
+
+// typeAlias
+@A
+public
+ typealias
+ A
+ <
+ B, // KNOWN_BUG_WHEN_TRUNCATED
+ C // KNOWN_BUG_WHEN_TRUNCATED
+ >
+ =
+ D
+
+// classDeclaration
+
+@A
+public
+ sealed
+ class
+ A
+ <
+ A, // KNOWN_BUG_WHEN_TRUNCATED
+ B // KNOWN_BUG_WHEN_TRUNCATED
+ >
+ // primaryConstructor
+ public
+ constructor
+ (
+ // classParameters
+ val
+ x
+ :
+ Foo
+ =
+ foo,
+ var
+ y
+ :
+ Foo
+ =
+ foo
+ )
+ :
+ // delegationSepcifiers
+ Base
+ by
+ x +
+ x,
+ B,
+ C,
+ D
+ .
+ (Int)
+ ->
+ Int
+ where
+ @A
+ A
+ :
+ A,
+ B
+ :
+ B {
+
+ public
+ interface
+ A {
+ fun a(): Int
+ fun b(): Int
+ }
+
+ // typeParameters
+ data
+ class
+ Bar<
+ @A // KNOWN_BUG_WHEN_TRUNCATED
+ out
+ A // KNOWN_BUG_WHEN_TRUNCATED
+ :
+ A,
+ @A // KNOWN_BUG_WHEN_TRUNCATED
+ in // KNOWN_BUG_WHEN_TRUNCATED
+ A
+ :
+ A
+ >
+ { // brace on its own line
+ fun a() {
+ }
+ }
+
+ inner
+ enum
+ class
+ A(val x: Int) {
+ // enumClassBody
+ A(1), B(1),
+ C(1) {
+ override fun a() = 2
+ }, D(1) {
+ override fun a() = 3
+ },
+ E(1);
+
+ fun a(): Int = x
+ }
+
+ // typeConstraints
+ public
+ class
+ Foo<
+ A // KNOWN_BUG_WHEN_TRUNCATED
+ >
+ :
+ X,
+ Y
+ where
+ A
+ :
+ B,
+ A
+ :
+ C {
+ }
+
+ public class Foo: X, Y
+ where A
+ :
+ B,
+ A
+ :
+ C {
+ }
+
+ public class Foo: X, Y where
+ A
+ :
+ B,
+ A
+ :
+ C {
+ }
+
+ public class Foo: X, Y where A
+ :
+ B,
+ A
+ :
+ C {
+ }
+
+ fun <
+ A
+ > Foo.foo()
+ : A
+ where
+ A
+ :
+ B,
+ A
+ :
+ C {
+ }
+
+ fun <
+ A
+ > Foo.foo()
+ : A
+ where
+ A
+ :
+ B,
+ A
+ :
+ C
+ =
+ A()
+
+ val
+ <
+ A // KNOWN_BUG_WHEN_TRUNCATED
+ >
+ A
+ .
+ x
+ where
+ A
+ :
+ B,
+ A
+ :
+ C
+ =
+ 1
+
+ val f = fun A.(): A
+ where
+ A:
+ B,
+ A:
+ C {
+ }
+
+ class Foo where T: A
+ , T: B
+ , T: C
+
+ // anonymousInitializer
+ init {
+ a()
+ }
+
+ // companionObject
+ public
+ companion
+ object
+ A
+ :
+ B {
+ fun foo() {
+ }
+ }
+
+ // functionDeclaration
+ public
+ fun
+ <
+ A // KNOWN_BUG_WHEN_TRUNCATED
+ >
+ A
+ .
+ foo
+ // functionValueParameters // KNOWN_BUG
+ (
+ a: Int = 1
+ )
+ :
+ A
+ where
+ A : A {
+ a()
+ }
+
+ fun
+ foo(x)
+ =
+ x + 1
+
+ // propertyDeclaration
+ public
+ val
+ <
+ A // KNOWN_BUG_WHEN_TRUNCATED
+ >
+ A
+ .
+ @A
+ a
+ :
+ A
+ where
+ A
+ :
+ A
+ =
+ a
+ public get() = 1
+ public set(value) {
+ foo(value)
+ }
+
+ // delegated property
+ var
+ x : Int
+ by
+ foo
+
+ // multiVariableDeclaration
+ public
+ var
+ (
+ x: Int,
+ y: Int
+ )
+
+ // getter/setter with default implementation
+ var x = 1
+ @A get
+ @A set
+
+ // objectDeclaration
+ public
+ object
+ Foo
+ :
+ Bar {
+ fun foo() {
+ bar()
+ }
+ }
+
+ // secondaryConstructor
+ public
+ constructor
+ (
+ )
+ :
+ this
+ (
+ 1
+ ) {
+ a()
+ }
+
+ public
+ constructor
+ (
+ )
+ :
+ super
+ (
+ 1
+ )
+
+ // dynamic type
+ var x: dynamic = 1
+
+ // nullableType
+ var
+ x
+ :
+ A
+ <
+ X, // KNOWN_BUG_WHEN_TRUNCATED
+ *, // KNOWN_BUG_WHEN_TRUNCATED
+ out
+ Y,
+ in
+ Z
+ >
+ .
+ B
+ .
+ C
+ <
+ X // KNOWN_BUG_WHEN_TRUNCATED
+ >
+ ?
+ ?
+ ?
+ =
+ null
+
+ var
+ x
+ :
+ (
+ Int
+ )
+ ?
+ =
+ null
+
+ // functionType
+ var
+ f
+ :
+ // parehthesized nullable receiver type
+ (
+ A
+ )
+ ?
+ .
+ (
+ Int
+ )
+ ->
+ (
+ Int
+ )
+ ->
+ C
+
+ // definitelyNonNullableType
+ fun foo(
+ x:
+ A
+ &
+ Any
+ ) {
+ }
+
+ // value class
+
+ value
+ class Foo { // KNOWN_BUG
+ } // KNOWN_BUG
+}
+
+
+// statements
+fun foo() {
+ //explicit semicolons
+ /* aaa */ a(); b();
+ c();
+
+ // annotation
+ @A
+ @B
+ .D(aaa) @C
+ @[
+ A
+ B
+ C
+ ]
+ // label
+ aaa@
+ a()
+
+ // forStatement
+ for (
+ @A
+ a
+ :
+ A
+ in
+ aaa
+ ) {
+ a()
+ }
+
+ for
+ (
+ @A
+ (x, y)
+ in
+ aaa
+ )
+ {
+ a()
+ }
+
+ for (
+ a in aaa
+ )
+ a()
+
+ // whileStatement
+ while (
+ a()
+ ) {
+ a()
+ }
+
+ while
+ (
+ a()
+ )
+ {
+ a()
+ }
+
+ while (
+ a()
+ )
+ a()
+
+ while (
+ a()
+ )
+ ;
+
+ // doWhileStatement
+ do {
+ a()
+ } while (
+ a()
+ )
+
+ do
+ {
+ a()
+ }
+ while
+ (
+ a()
+ )
+
+ do
+ a() +
+ b()
+ while (a())
+
+ do
+ while (a())
+
+ while (a())
+ do
+ do
+ while(a())
+ while(a())
+
+ while(a())
+ do
+ while(a())
+
+ // assignment
+ (x, y) = // Line breaks are prohibited before assignment operators.
+ a()
+
+ aaa[x] =
+ 1
+
+ a
+ .
+ b
+ ?.
+ c
+ .
+ d =
+ e
+
+ a
+ .
+ b +=
+ 1
+
+ a
+ .
+ b -=
+ 1
+
+ a
+ .
+ b *=
+ 1
+
+ a
+ .
+ b %=
+ 1
+
+ // expression
+ // various operators
+ val x =
+ a
+ ||
+ b
+ &&
+ c
+
+ val x =
+ a == // Line breaks are prohibited before equality operators
+ b
+
+ val x =
+ a !=
+ b
+
+ val x =
+ a ===
+ b
+
+ val x =
+ a !==
+ g
+
+ val x =
+ a < // Line breaks are prohibited before comparison operators
+ b
+
+ val x =
+ a >
+ b
+
+ val x =
+ a <=
+ b
+
+ val x =
+ a >=
+ b
+
+ val x =
+ foo(a) in // Line breaks are prohibited before in/is operators
+ b
+
+ val x =
+ a !in
+ b
+
+ when (a()) {
+ 1 -> a()
+ // Line breaks are prohibited before in/is operators, So the following
+ // line should not be indented.
+ in aaa -> a()
+ !in aaa -> a()
+ }
+
+ val x =
+ a is
+ b
+
+ val x =
+ a !is
+ b
+
+ when (a()) {
+ 1 -> a()
+ // Line breaks are prohibited before in/is operators, So the following
+ // line should not be indented.
+ is X -> a()
+ !is X -> a()
+ }
+
+ val x =
+ a
+ ?:
+ b
+
+ // infixFunctionCall
+ val x =
+ a shl // Line breaks are allowed after infix function.
+ b // KNOWN_BUG
+ val y = 1 // KNOWN_BUG
+
+ var shl = 1
+ val x = shl shl shl
+ shl < 100 && foo() // this is not a continuation of the previous line.
+
+ var shl = 1
+ val x = shl shl
+ shl < 100 && foo() // this is a continuation of the previous line. // KNOWN_BUG
+ val y = 1 // KNOWN_BUG
+
+ // This is not a infix function call; line breaks are
+ // prohibited before infix function.
+ val x =
+ a
+ f (b) // So this line should not indented.
+
+ val x =
+ a .. // Line breaks are prohibited before range operator.
+ b
+
+ val x =
+ a ..< // Line breaks are prohibited before closed range operator.
+ b
+
+ val x =
+ a + // Line breaks are prohibited before additive operators.
+ b -
+ c
+
+ a()
+ +a() // So this line should not be indented.
+ -a() // Nor should this line.
+
+ val x =
+ a * // Line breaks are prohibited before multiplicative operators.
+ b /
+ c %
+ d
+
+ val x =
+ a
+ as
+ A
+ as?
+ B
+
+ // prefixUnaryExpression
+ val x =
+ @a
+ a@ // label
+ +
+ -
+ ++
+ a // KNOWN_BUG
+
+ val x = // KNOWN_BUG
+ --
+ a // KNOWN_BUG
+
+ val x = // KNOWN_BUG
+ !
+ a // KNOWN_BUG
+
+ // postfixUnaryExpression // KNOWN_BUG
+ val x = // KNOWN_BUG
+ a++ // Line breaks are prohibited before postfix operators.
+
+ val x =
+ a
+ ++ a // So this line should not be indented.
+
+ val x =
+ a--
+
+ val x =
+ a
+ -- a // This line too.
+
+ var shl = 1
+ val x = shl shl shl ++
+ shl < 100 && foo() // this is not a continuation of the previous line.
+
+ var shl = 1
+ val x = shl shl ++
+ shl < 100 && foo() // this is a continuation of the previous line. // KNOWN_BUG
+
+ val x = // KNOWN_BUG
+ a!!
+
+ val x = foo()!!
+ foo() // this is not a continuation of the previous line.
+
+ val x = !!
+ foo() // this is a continuation of the previous line. // KNOWN_BUG
+
+ val x = // KNOWN_BUG
+ f< // Line breaks are prohibited before type arguments.
+ A // KNOWN_BUG_WHEN_TRUNCATED
+ >( // Line breaks are prohibited before function arguments.
+ x
+ )[ // Line breaks are prohibited before subscript.
+ x
+ ]
+ .
+ a
+ .
+ b
+
+ // lambda arguments
+ val x = f()
+ {
+ a()
+ }
+
+ val x = f()
+ @A
+ a@
+ {
+ a()
+ }
+
+ val x = f() @A a@ {
+ a()
+ }
+
+ val x = f
+ {
+ a()
+ }
+
+ val x = f
+ @A
+ a@
+ {
+ a()
+ }
+
+ val x = f @A a@ {
+ a()
+ }
+
+ val x = x
+ .foo {
+ a
+ }
+ .bar {
+ a
+ }
+
+ val x = x.foo {
+ a
+ }.bar {
+ a
+ }
+
+ val x =
+ f // Line breaks are prohibited before function arguments.
+ (1 + 1).also { print(it) } // So this line should not be indented.
+
+ val x =
+ a // Line breaks are prohibited before function arguments.
+ [g()].forEach { print(it) } // So this line should not be indented.
+
+ // parenthesizedExpression
+ val x = (
+ a()
+ )
+
+ // collectionLiteral
+ @A(x = [
+ 1, 2, 3,
+ /* aaa */ 4, 5, 6,
+ 7, 8, 9
+ ])
+ a()
+
+ // CharacterLiteral
+ val x =
+ 'a'
+
+ val x =
+ '\''
+
+ val x =
+ '"'
+
+ // stringLiteral
+ val x = "abc\"def${
+ foo("abc")
+ }ghi${
+ "aaa\${ // dollar sign cannot be escaped
+ 1
+ }bbb"
+ }jkl"
+
+ val x = """a
+ "b"
+ c${
+ foo("""a
+ b
+ c
+ """)
+ }d
+ e
+ f${
+ """aaa\${
+ 1
+ }bbb"""
+ }ghi"""
+
+ val x =
+ a("'")
+
+ // lambdaLiteral
+ val f: () -> Unit = {
+ a()
+ a()
+ a()
+ }
+
+ val f: () -> Unit = { ->
+ a()
+ a()
+ a()
+ }
+
+ val f: () -> Unit = {
+ ->
+ a()
+ a()
+ a()
+ }
+
+ val f: () -> Unit = { x,
+ @A
+ y
+ :
+ Int,
+ (
+ z1,
+ z2
+ )
+ :
+ AAA
+ ->
+ a()
+ a()
+ a()
+ }
+
+ val f: () -> Unit = {
+ x,
+ y,
+ z
+ ->
+ a()
+ a()
+ a()
+ }
+
+ val f: () -> Unit = {
+ (x,
+ y,
+ z)
+ ->
+ a()
+ a()
+ a()
+ }
+
+ // functionLiteral
+
+ val f =
+ fun
+ A
+ .
+ (
+ x: Int
+ )
+ :
+ AAA
+ where
+ A
+ :
+ A,
+ B
+ :
+ B {
+ a() // KNOWN_BUG
+ } // KNOWN_BUG
+
+ val f = fun
+ {
+ a()
+ }
+
+ // objectLiteral
+ val object: A by a, B by b {
+ fun foo() {
+ }
+ }
+
+ val x =
+ object
+ :
+ A,
+ B
+ by
+ b,
+ C {
+ fun foo() {
+ }
+ }
+
+ val x = object
+ {
+ fun foo() {
+ }
+ }
+
+ // thisExpression
+ val x =
+ this
+
+ val x =
+ this@Foo
+
+ // superExpression
+ val x =
+ super
+
+ val x =
+ super@Foo
+
+ val x =
+ super<
+ Int // KNOWN_BUG_WHEN_TRUNCATED
+ >@Foo
+
+ // ifExpression
+ val x = if (
+ a +
+ 1
+ ) {
+ a()
+ }
+
+ val x = if
+ (
+ a +
+ 1
+ )
+ {
+ a()
+ }
+
+ val x = if (
+ a
+ )
+ a()
+
+ val x = if (
+ a
+ )
+ ;
+
+ val x = if (
+ a
+ ) {
+ a()
+ } else {
+ a()
+ }
+
+ val x = if
+ (
+ a
+ )
+ {
+ a()
+ }
+ else
+ {
+ a()
+ }
+
+ val x = if (
+ a
+ )
+ a()
+ else
+ a()
+
+ val x = if (
+ a
+ )
+ ;
+ else
+ ;
+
+ val x = if (
+ a
+ )
+ else
+ ;
+
+ val x =
+ if (
+ a
+ ) {
+ a()
+ } else {
+ a()
+ }
+
+ val x = if (foo) 1
+ else 2 // should be indented?
+
+ val x =
+ if (foo) 1
+ else 2
+
+ val x = if (foo) 1 +
+ 1
+ else 2 +
+ 1
+
+ val x = if (foo)
+ if (bar)
+ aaa() +
+ 1
+ else
+ if (baz)
+ if (aaa) aaa else bbb +
+ 1
+ else
+ if (aaa) aaa else
+ bbb +
+ 1
+ else
+ if (bar)
+ if (aaa)
+ if (bbb)
+ ccc +
+ 1 +
+ 2
+ else
+ ccc +
+ 1 +
+ 2
+ else
+ ccc +
+ 1 +
+ 2
+ else
+ aaa() +
+ 1
+
+ val x = if (foo)
+ while (false) {
+ aaa() +
+ 1
+ }
+ else
+ while (false) {
+ aaa() +
+ 1
+ }
+
+ val x = if (a)
+ foo() +
+ 1
+ else if (e)
+ foo() +
+ 1
+ else
+ foo() +
+ 1
+
+ val x = if (a) {
+ foo() +
+ 1
+ } else if (e) {
+ foo() +
+ 1
+ } else {
+ foo() +
+ 1
+ }
+
+ val x = if (a) {
+ foo() +
+ 1
+ } else
+ if (e) {
+ foo() +
+ 1
+ } else {
+ foo() +
+ 1
+ }
+
+ val x = if (a)
+ {
+ foo() +
+ 1
+ }
+ else if (e)
+ {
+ foo() +
+ 1
+ }
+ else
+ {
+ foo() +
+ 1
+ }
+
+ val x = if (a) if (a) 1 +
+ 1 else 1 else
+ if (a) if (a) 1 +
+ 1 else 1 else
+ 1
+
+ // whenExpression
+
+ val x = when (
+ @A
+ val
+ a
+ =
+ a()
+ ) {
+ a(), b(),
+ c(), d()
+ ->
+ {
+ a()
+ }
+
+ a() -> {
+ a()
+ }
+
+ in
+ a()
+ ->
+ {
+ a ()
+ }
+
+ is
+ A
+ ->
+ {
+ }
+
+ a()
+ ->
+ if (x) {
+ 1
+ } else {
+ 2
+ }
+
+ if (x)
+ 1
+ else
+ 2
+ ->
+ if (x)
+ 1
+ else
+ 2
+
+ else
+ ->
+ {
+ a()
+ }
+ }
+
+ val x = when
+ (
+ a()
+ )
+ {
+ a -> 1
+ }
+
+ // tryExpression
+ val x = try {
+ a()
+ } catch(@A e: E) {
+ a()
+ } catch(@A e: E) {
+ a()
+ } finally {
+ a()
+ }
+
+ val x =
+ try {
+ a()
+ } catch(@A e: E) {
+ a()
+ } catch(@A e: E) {
+ a()
+ } finally {
+ a()
+ }
+
+ val x = try
+ {
+ a()
+ }
+ catch
+ (
+ @A e: E
+ )
+ {
+ a()
+ }
+ finally
+ {
+ a()
+ }
+
+ val x = try {
+ a()
+ } finally {
+ a()
+ }
+
+ val x = try
+ {
+ a()
+ }
+ finally
+ {
+ a()
+ }
+
+ // jumpExpression
+ val x =
+ throw
+ a()
+
+ val x =
+ return a() // Line breaks are prohibited after return.
+
+ val x =
+ return // Line breaks are prohibited after return.
+ a() // So this line should not be indented.
+
+ val x =
+ return@A a() // Line breaks are prohibited after return.
+
+ val x =
+ return@A // Line breaks are prohibited after return.
+ a() // So this line should not be indented.
+
+ val x =
+ continue
+
+ val x =
+ continue@A
+
+ val x =
+ break
+
+ val x =
+ break@A
+
+ // callableReference
+ val x =
+ Foo
+ ::
+ foo
+
+ val x =
+ Foo
+ ::
+ class
+
+ // typeModifier
+ val f:
+ suspend
+ () -> Unit
+ =
+ suspend
+ {
+ a()
+ }
+}
+
+class Foo: Base {
+ // memberModifier
+ public
+ override
+ fun f() {
+ }
+
+ public
+ lateinit
+ var x: Int
+
+
+ // visibilityModifier
+ override
+ public
+ fun f() {
+ }
+
+ override
+ private
+ fun f() {
+ }
+
+ override
+ internal
+ fun f() {
+ }
+
+ override
+ protected
+ fun f() {
+ }
+
+ // functionModifier
+ public
+ tailrec
+ infix
+ inline
+ fun A.f(): A {
+ return a()
+ }
+
+ public
+ operator
+ fun A.unaryPlus(): A {
+ return a()
+ }
+
+ public
+ suspend
+ fun foo() {
+ a()
+ }
+}
+
+public
+ external
+ fun foo() {
+ a()
+}
+
+class Foo {
+ // propertyModifier
+ public
+ const
+ val
+ x = 1
+}
+
+// inheritanceModifier
+public
+ abstract
+ class Foo {
+ fun foo() {
+ bar()
+ }
+}
+
+public
+ final
+ class Foo {
+ fun foo() {
+ bar()
+ }
+}
+
+public
+ open
+ class Foo {
+ fun foo() {
+ }
+}
+
+class Foo {
+ // parameterModifier
+ fun foo(
+ @A
+ crossinline
+ body: () -> Unit
+ ) {
+ }
+
+ fun foo(
+ @A
+ noinline
+ body: () -> Unit
+ ) {
+ }
+
+ fun foo(
+ @A
+ vararg
+ a: A
+ ) {
+ }
+
+ // reificationModifier
+ inline fun <
+ @A
+ reified
+ T // KNOWN_BUG_WHEN_TRUNCATED
+ > foo() {
+ a()
+ }
+}
+
+// platformModifier
+public
+ expect
+ class Foo {
+ fun foo()
+}
+
+public
+ actual
+ class Foo {
+ fun foo()
+}
+
+
+// Ambiguous commas, colons, curly brackets, and objects.
+
+class C: A by object: B1,
+ B2 {
+ fun foo() {}
+ },
+ B by object: B3,
+ B4 {
+ fun foo() {}
+ } {
+ fun foo(x: T): Int {
+ return when (x) {
+ object: B1 by object: B1 {
+ fun foo() {}
+ },
+ B2 {
+ fun foo() {}
+ },
+ object: B3,
+ B4 {
+ fun foo() {}
+ } ->
+ 1
+
+ else ->
+ 2
+ }
+ }
+}
+
+// Curly braces may appar at:
+// classBody
+// class (optional)
+// interface (optional)
+// companionObject (optional)
+// objectDeclaration (optional)
+// enumEntry (optional)
+// objectLiteral
+// block
+// init
+// functionBody
+// fun (optional)
+// getter
+// setter
+// anonymousFunction
+// constructor (optional)
+// controlStructureBody (optional)
+// for
+// while
+// do
+// if
+// else
+// whenEntry
+// try
+// catch
+// finally
+// lambda
+// when
+
+class C:
+ A by foo bar { // infix function call
+ aaa() // this is not a class body // KNOWN_BUG
+ } { // KNOWN_BUG
+ // this is the class body
+
+ fun foo() {
+ aaa()
+ }
+}
+
+// Ambiguous arrows
+
+val f = { g:
+ (Int) ->
+ (Int) ->
+ Int ->
+ g(1, 2)
+}
+
+when (x) {
+ 1 ->
+ f as
+ (Int) ->
+ Int // KNOWN_BUG
+ f as (Int) ->
+ Int ->
+ f as
+ (Int) ->
+ Int // KNOWN_BUG
+ f(1) ->
+ f as
+ (Int) ->
+ Int // KNOWN_BUG
+ (f) ->
+ f as
+ (Int) ->
+ Int // KNOWN_BUG
+ is (Int) ->
+ Int ->
+ f as
+ (Int) ->
+ Int // KNOWN_BUG
+ is Foo<(Int) ->
+ Int, // KNOWN_BUG
+ (Int) -> // KNOWN_BUG
+ Int> -> // KNOWN_BUG
+ f as
+ (Int) ->
+ Int // KNOWN_BUG
+ 1 < (2), (f) ->
+ f as
+ (Int) ->
+ Int // KNOWN_BUG
+ else ->
+ f as
+ (Int) ->
+ Int // KNOWN_BUG
+}
+
+// various identifiers
+
+val `abc` =
+ 1
+
+val `abc def 'ghi "aaa ${ aaa \` = // backquotes cannot be escaped
+ a()
+
+// UNICODE_CLASS_ND
+val _0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८९০১২৩৪৫৬৭৮৯੦੧੨੩੪੫੬੭੮੯૦૧૨૩૪૫૬૭૮૯୦୧୨୩୪୫୬୭୮୯௦௧௨௩௪௫௬௭௮௯౦౧౨౩౪౫౬౭౮౯೦೧೨೩೪೫೬೭೮೯൦൧൨൩൪൫൬൭൮൯෦෧෨෩෪෫෬෭෮෯๐๑๒๓๔๕๖๗๘๙໐໑໒໓໔໕໖໗໘໙༠༡༢༣༤༥༦༧༨༩၀၁၂၃၄၅၆၇၈၉႐႑႒႓႔႕႖႗႘႙០១២៣៤៥៦៧៨៩᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙᥆᥇᥈᥉᥊᥋᥌᥍᥎᥏᧐᧑᧒᧓᧔᧕᧖᧗᧘᧙᪀᪁᪂᪃᪄᪅᪆᪇᪈᪉᪐᪑᪒᪓᪔᪕᪖᪗᪘᪙᭐᭑᭒᭓᭔᭕᭖᭗᭘᭙᮰᮱᮲᮳᮴᮵᮶᮷᮸᮹᱀᱁᱂᱃᱄᱅᱆᱇᱈᱉᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙꘠꘡꘢꘣꘤꘥꘦꘧꘨꘩꣐꣑꣒꣓꣔꣕꣖꣗꣘꣙꤀꤁꤂꤃꤄꤅꤆꤇꤈꤉꧐꧑꧒꧓꧔꧕꧖꧗꧘꧙꧰꧱꧲꧳꧴꧵꧶꧷꧸꧹꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙꯰꯱꯲꯳꯴꯵꯶꯷꯸꯹0123456789𐒠𐒡𐒢𐒣𐒤𐒥𐒦𐒧𐒨𐒩 =
+ 1
+
+// Some characters are not supported while they are in the unicode category Nd.
+// val _𐴰 =
+// 1
+// val _𐴱 =
+// 1
+// val _𐴲 =
+// 1
+// val _𐴳 =
+// 1
+// val _𐴴 =
+// 1
+// val _𐴵 =
+// 1
+// val _𐴶 =
+// 1
+// val _𐴷 =
+// 1
+// val _𐴸 =
+// 1
+// val _𐴹 =
+// 1
+val _𑁦 =
+ 1
+val _𑁧 =
+ 1
+val _𑁨 =
+ 1
+val _𑁩 =
+ 1
+val _𑁪 =
+ 1
+val _𑁫 =
+ 1
+val _𑁬 =
+ 1
+val _𑁭 =
+ 1
+val _𑁮 =
+ 1
+val _𑁯 =
+ 1
+val _𑃰 =
+ 1
+val _𑃱 =
+ 1
+val _𑃲 =
+ 1
+val _𑃳 =
+ 1
+val _𑃴 =
+ 1
+val _𑃵 =
+ 1
+val _𑃶 =
+ 1
+val _𑃷 =
+ 1
+val _𑃸 =
+ 1
+val _𑃹 =
+ 1
+val _𑄶 =
+ 1
+val _𑄷 =
+ 1
+val _𑄸 =
+ 1
+val _𑄹 =
+ 1
+val _𑄺 =
+ 1
+val _𑄻 =
+ 1
+val _𑄼 =
+ 1
+val _𑄽 =
+ 1
+val _𑄾 =
+ 1
+val _𑄿 =
+ 1
+val _𑇐 =
+ 1
+val _𑇑 =
+ 1
+val _𑇒 =
+ 1
+val _𑇓 =
+ 1
+val _𑇔 =
+ 1
+val _𑇕 =
+ 1
+val _𑇖 =
+ 1
+val _𑇗 =
+ 1
+val _𑇘 =
+ 1
+val _𑇙 =
+ 1
+val _𑋰 =
+ 1
+val _𑋱 =
+ 1
+val _𑋲 =
+ 1
+val _𑋳 =
+ 1
+val _𑋴 =
+ 1
+val _𑋵 =
+ 1
+val _𑋶 =
+ 1
+val _𑋷 =
+ 1
+val _𑋸 =
+ 1
+val _𑋹 =
+ 1
+val _𑑐 =
+ 1
+val _𑑑 =
+ 1
+val _𑑒 =
+ 1
+val _𑑓 =
+ 1
+val _𑑔 =
+ 1
+val _𑑕 =
+ 1
+val _𑑖 =
+ 1
+val _𑑗 =
+ 1
+val _𑑘 =
+ 1
+val _𑑙 =
+ 1
+val _𑓐 =
+ 1
+val _𑓑 =
+ 1
+val _𑓒 =
+ 1
+val _𑓓 =
+ 1
+val _𑓔 =
+ 1
+val _𑓕 =
+ 1
+val _𑓖 =
+ 1
+val _𑓗 =
+ 1
+val _𑓘 =
+ 1
+val _𑓙 =
+ 1
+val _𑙐 =
+ 1
+val _𑙑 =
+ 1
+val _𑙒 =
+ 1
+val _𑙓 =
+ 1
+val _𑙔 =
+ 1
+val _𑙕 =
+ 1
+val _𑙖 =
+ 1
+val _𑙗 =
+ 1
+val _𑙘 =
+ 1
+val _𑙙 =
+ 1
+val _𑛀 =
+ 1
+val _𑛁 =
+ 1
+val _𑛂 =
+ 1
+val _𑛃 =
+ 1
+val _𑛄 =
+ 1
+val _𑛅 =
+ 1
+val _𑛆 =
+ 1
+val _𑛇 =
+ 1
+val _𑛈 =
+ 1
+val _𑛉 =
+ 1
+val _𑜰 =
+ 1
+val _𑜱 =
+ 1
+val _𑜲 =
+ 1
+val _𑜳 =
+ 1
+val _𑜴 =
+ 1
+val _𑜵 =
+ 1
+val _𑜶 =
+ 1
+val _𑜷 =
+ 1
+val _𑜸 =
+ 1
+val _𑜹 =
+ 1
+val _𑣠 =
+ 1
+val _𑣡 =
+ 1
+val _𑣢 =
+ 1
+val _𑣣 =
+ 1
+val _𑣤 =
+ 1
+val _𑣥 =
+ 1
+val _𑣦 =
+ 1
+val _𑣧 =
+ 1
+val _𑣨 =
+ 1
+val _𑣩 =
+ 1
+val _𑱐 =
+ 1
+val _𑱑 =
+ 1
+val _𑱒 =
+ 1
+val _𑱓 =
+ 1
+val _𑱔 =
+ 1
+val _𑱕 =
+ 1
+val _𑱖 =
+ 1
+val _𑱗 =
+ 1
+val _𑱘 =
+ 1
+val _𑱙 =
+ 1
+// val _𑵐 =
+// 1
+// val _𑵑 =
+// 1
+// val _𑵒 =
+// 1
+// val _𑵓 =
+// 1
+// val _𑵔 =
+// 1
+// val _𑵕 =
+// 1
+// val _𑵖 =
+// 1
+// val _𑵗 =
+// 1
+// val _𑵘 =
+// 1
+// val _𑵙 =
+// 1
+// val _𑶠 =
+// 1
+// val _𑶡 =
+// 1
+// val _𑶢 =
+// 1
+// val _𑶣 =
+// 1
+// val _𑶤 =
+// 1
+// val _𑶥 =
+// 1
+// val _𑶦 =
+// 1
+// val _𑶧 =
+// 1
+// val _𑶨 =
+// 1
+// val _𑶩 =
+// 1
+val _𖩠 =
+ 1
+val _𖩡 =
+ 1
+val _𖩢 =
+ 1
+val _𖩣 =
+ 1
+val _𖩤 =
+ 1
+val _𖩥 =
+ 1
+val _𖩦 =
+ 1
+val _𖩧 =
+ 1
+val _𖩨 =
+ 1
+val _𖩩 =
+ 1
+val _𖭐 =
+ 1
+val _𖭑 =
+ 1
+val _𖭒 =
+ 1
+val _𖭓 =
+ 1
+val _𖭔 =
+ 1
+val _𖭕 =
+ 1
+val _𖭖 =
+ 1
+val _𖭗 =
+ 1
+val _𖭘 =
+ 1
+val _𖭙 =
+ 1
+val _𝟎 =
+ 1
+val _𝟏 =
+ 1
+val _𝟐 =
+ 1
+val _𝟑 =
+ 1
+val _𝟒 =
+ 1
+val _𝟓 =
+ 1
+val _𝟔 =
+ 1
+val _𝟕 =
+ 1
+val _𝟖 =
+ 1
+val _𝟗 =
+ 1
+val _𝟘 =
+ 1
+val _𝟙 =
+ 1
+val _𝟚 =
+ 1
+val _𝟛 =
+ 1
+val _𝟜 =
+ 1
+val _𝟝 =
+ 1
+val _𝟞 =
+ 1
+val _𝟟 =
+ 1
+val _𝟠 =
+ 1
+val _𝟡 =
+ 1
+val _𝟢 =
+ 1
+val _𝟣 =
+ 1
+val _𝟤 =
+ 1
+val _𝟥 =
+ 1
+val _𝟦 =
+ 1
+val _𝟧 =
+ 1
+val _𝟨 =
+ 1
+val _𝟩 =
+ 1
+val _𝟪 =
+ 1
+val _𝟫 =
+ 1
+val _𝟬 =
+ 1
+val _𝟭 =
+ 1
+val _𝟮 =
+ 1
+val _𝟯 =
+ 1
+val _𝟰 =
+ 1
+val _𝟱 =
+ 1
+val _𝟲 =
+ 1
+val _𝟳 =
+ 1
+val _𝟴 =
+ 1
+val _𝟵 =
+ 1
+val _𝟶 =
+ 1
+val _𝟷 =
+ 1
+val _𝟸 =
+ 1
+val _𝟹 =
+ 1
+val _𝟺 =
+ 1
+val _𝟻 =
+ 1
+val _𝟼 =
+ 1
+val _𝟽 =
+ 1
+val _𝟾 =
+ 1
+val _𝟿 =
+ 1
+// val _𞅀 =
+// 1
+// val _𞅁 =
+// 1
+// val _𞅂 =
+// 1
+// val _𞅃 =
+// 1
+// val _𞅄 =
+// 1
+// val _𞅅 =
+// 1
+// val _𞅆 =
+// 1
+// val _𞅇 =
+// 1
+// val _𞅈 =
+// 1
+// val _𞅉 =
+// 1
+// val _𞋰 =
+// 1
+// val _𞋱 =
+// 1
+// val _𞋲 =
+// 1
+// val _𞋳 =
+// 1
+// val _𞋴 =
+// 1
+// val _𞋵 =
+// 1
+// val _𞋶 =
+// 1
+// val _𞋷 =
+// 1
+// val _𞋸 =
+// 1
+// val _𞋹 =
+// 1
+val _𞥐 =
+ 1
+val _𞥑 =
+ 1
+val _𞥒 =
+ 1
+val _𞥓 =
+ 1
+val _𞥔 =
+ 1
+val _𞥕 =
+ 1
+val _𞥖 =
+ 1
+val _𞥗 =
+ 1
+val _𞥘 =
+ 1
+val _𞥙 =
+ 1
+
+// UNICODE_CLASS_LL
+// UNICODE_CLASS_LM
+// UNICODE_CLASS_LO
+// UNICODE_CLASS_LT
+// UNICODE_CLASS_LU
+val aʰªDžA =
+ 1
+
+// UNICODE_CLASS_NL (rejected for some reason)
+// val ᛮ =
+// 1
diff --git a/test/sample.kt b/test/sample.kt
index 7dd47b5..cb8c56a 100644
--- a/test/sample.kt
+++ b/test/sample.kt
@@ -13,7 +13,7 @@ import bar.Bar as bBar
/****************************************************************
Multiline comment
without leading "*"
-****************************************************************/
+****************************************************************/ // KNOWN_BUG
fun sum(a: Int, b: Int): Int {
return a + b
@@ -367,7 +367,7 @@ var setterVisibility: String = "abc"
private set // the setter is private and has the default implementation
var setterWithAnnotation: Any? = null
-@Inject set // annotate the setter with Inject
+ @Inject set // annotate the setter with Inject
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
@@ -555,8 +555,8 @@ fun > sort(list: List) {
}
fun cloneWhenGreater(list: List, threshold: T): List
-where T : Comparable,
-T : Cloneable {
+ where T : Comparable,
+ T : Cloneable {
return list.filter { it > threshold }.map { it.clone() }
}
@@ -591,8 +591,7 @@ window.addMouseListener(
override fun mouseEntered(e: MouseEvent) {
// ...
}
- }
-)
+ })
val adHoc = object {
var x: Int = 0
@@ -613,8 +612,7 @@ fun countClicks(window: JComponent) {
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
- }
- )
+ })
// ...
}
@@ -683,13 +681,13 @@ val doubled = ints.map { it -> it * 2 }
strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }
max(strings, { a, b -> a.length < b.length
-})
+ })
fun max(collection: Collection, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
- max = it
+ max = it
return max
}
@@ -733,8 +731,10 @@ inline fun TreeNode.findParentOfType(): T? {
class Test {
fun tryAdd(a: Int?, b: Int?) : Int? {
var result: Int? = null
- a?.let { lhs ->
- b?.let { rhs ->
+ a?.let {
+ lhs ->
+ b?.let {
+ rhs ->
result = lhs + rhs
}
}