From c95216dc5defcfc36d884f40731ab8f7e1e7778c Mon Sep 17 00:00:00 2001 From: Dmitry Gridin Date: Sun, 14 Jun 2020 14:21:25 +0700 Subject: [PATCH] KotlinLikeLangLineIndentProvider: cleanup code Part of #KT-22211 --- .../KotlinLikeLangLineIndentProvider.kt | 819 +++++++++--------- 1 file changed, 412 insertions(+), 407 deletions(-) diff --git a/idea/line-indent-provider/src/org/jetbrains/kotlin/idea/formatter/lineIndent/KotlinLikeLangLineIndentProvider.kt b/idea/line-indent-provider/src/org/jetbrains/kotlin/idea/formatter/lineIndent/KotlinLikeLangLineIndentProvider.kt index dde721e03a0..dcaae86f567 100644 --- a/idea/line-indent-provider/src/org/jetbrains/kotlin/idea/formatter/lineIndent/KotlinLikeLangLineIndentProvider.kt +++ b/idea/line-indent-provider/src/org/jetbrains/kotlin/idea/formatter/lineIndent/KotlinLikeLangLineIndentProvider.kt @@ -128,407 +128,6 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider } } - private fun IndentCalculatorFactory.createIndentCalculatorForBrace( - before: SemanticEditorPosition, - after: SemanticEditorPosition, - leftBraceType: SemanticEditorPosition.SyntaxElement, - rightBraceType: SemanticEditorPosition.SyntaxElement - ): IndentCalculator? { - val leftBrace = before.copyAnd { - it.moveToLeftParenthesisBackwardsSkippingNested(leftBraceType, rightBraceType) - } - - val indent = if (after.after().afterOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET).isAt(Comma)) - createAlignMultilineIndent(leftBrace) - else - Indent.getNoneIndent() - - return createIndentCalculator(indent, leftBrace.startOffset) - } - - private fun IndentCalculatorFactory.createIndentCalculatorForParenthesis( - leftParenthesis: SemanticEditorPosition, - currentPosition: SemanticEditorPosition, - rightParenthesis: SemanticEditorPosition, - offset: Int, - settings: KotlinIndentationAdjuster, - ): IndentCalculator? { - assert(leftParenthesis.isAt(LeftParenthesis)) - assert(rightParenthesis.isAt(RightParenthesis)) - - // case only for caret before [RightParenthesis] - if (!currentPosition.hasLineBreaksAfter(offset)) { - val indentForParentheses by lazy { - if (settings.alignWhenMultilineFunctionParentheses) createAlignMultilineIndent(leftParenthesis) else Indent.getNoneIndent() - } - - findFunctionKeywordBeforeIdentifier(leftParenthesis.beforeIgnoringWhiteSpaceOrComment())?.let { - return createIndentCalculator(indentForParentheses, it.startOffset) - } - - // NB: this covered [KtTokens.CONSTRUCTOR_KEYWORD], [KtTokens.SET_KEYWORD], [KtTokens.GET_KEYWORD], [KtTokens.INIT_KEYWORD] as well - if (isSimilarToFunctionInvocation(leftParenthesis)) { - return createIndentCalculator(indentForParentheses, leftParenthesis.startOffset) - } - - if (isDestructuringDeclaration(leftParenthesis, rightParenthesis)) { - return createIndentCalculator(Indent.getNoneIndent(), leftParenthesis.startOffset) - } - - leftParenthesis.beforeIgnoringWhiteSpaceOrComment().let { keyword -> - if (keyword.isControlFlowKeyword()) { - return createIndentCalculator(Indent.getNoneIndent(), keyword.startOffset) - } - } - - val indentForBinaryExpression = if (settings.alignWhenMultilineBinaryExpression) - createAlignMultilineIndent(leftParenthesis) - else - Indent.getContinuationIndent() - - return createIndentCalculator(indentForBinaryExpression, leftParenthesis.startOffset) - } - - return null - } - - /** - * @receiver is position before '=' for expression body or '{' for block body - */ - private fun SemanticEditorPosition.isFunctionDeclaration(): Boolean = findFunctionDeclarationBeforeBody(this) != null - - /** - * @param endOfDeclaration is position before '=' for expression body or '{' for block body - */ - private fun findFunctionOrPropertyOrMultiDeclarationBefore(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? = - findFunctionDeclarationBeforeBody(endOfDeclaration) - ?: findPropertyDeclarationBeforeAssignment(endOfDeclaration) - ?: findMultiDeclarationBeforeAssignment(endOfDeclaration) - - /** - * `val (a, b) = 1 to 2` - * ^ - */ - private fun findMultiDeclarationBeforeAssignment(rightParenthesis: SemanticEditorPosition): SemanticEditorPosition? { - if (!rightParenthesis.isAt(RightParenthesis)) return null - return with(rightParenthesis.copy()) { - if (!moveBeforeParenthesesIfPossible()) return null - takeIf { isVarOrVal() } - } - } - - /** - * `val a = 5` - * `val a: Int = 5` - * `val List.a: Int = 5` - * `val List.a: Int = 5` - */ - private fun findPropertyDeclarationBeforeAssignment(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? { - // `val a = 5` - // this is a false positive for a declaration with explicit return type - if (endOfDeclaration.isAt(Identifier)) { - findPropertyKeywordBeforeIdentifier(endOfDeclaration)?.let { return it } - } - - return with(endOfDeclaration.copy()) { - // explicit type `fun a(): String` or `val a: String` - if (moveBeforeTypeQualifierIfPossible(true)) { - if (!isAt(Colon)) return null - moveBeforeIgnoringWhiteSpaceOrComment() - } - - findPropertyKeywordBeforeIdentifier(this) - } - } - - /** - * `val a = fun() { }` - * `val a = fun String.Companion????.() { }` - * `fun a() = 4` - * `fun a(): Int = 4` - * `fun Int.a(): Int = 4` - * `fun T.a(): Int = 4` - */ - private fun findFunctionDeclarationBeforeBody(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? = - with(endOfDeclaration.copy()) { - // explicit type `fun a(): String` - // ^ - if (moveBeforeTypeQualifierIfPossible(true)) { - if (!isAt(Colon)) return null - moveBeforeIgnoringWhiteSpaceOrComment() - } - - if (!moveBeforeParenthesesIfPossible()) return null - - findFunctionKeywordBeforeIdentifier(this) - } - - private fun findPropertyKeywordBeforeIdentifier(identifierPosition: SemanticEditorPosition): SemanticEditorPosition? { - if (!identifierPosition.isAt(Identifier)) return null - return with(identifierPosition.copy()) { - if (!moveBeforeTypeQualifierIfPossible(false)) return null - // `val List.prop` - moveBeforeTypeParametersIfPossible() - takeIf { it.isVarOrVal() } - } - } - - /** - * @return position of `fun` keyword before the declaration or null - * TODO: support [KtTokens.CONSTRUCTOR_KEYWORD], [KtTokens.INIT_KEYWORD]. Maybe [KtTokens.SET_KEYWORD], [KtTokens.GET_KEYWORD] (related to KT-39444) - */ - private fun findFunctionKeywordBeforeIdentifier(identifierPosition: SemanticEditorPosition): SemanticEditorPosition? { - // anonymous function `val a = fun() { }` - // ^ - if (identifierPosition.isAt(FunctionKeyword)) return identifierPosition - - return with(identifierPosition.copy()) { - moveBeforeWhileThisIsWhiteSpaceOrComment() - - // anonymous function with receiver `val a = fun String.Companion????.() { }` - // ^ - if (isAt(Dot)) { - moveBeforeIgnoringWhiteSpaceOrComment() - if (!moveBeforeTypeQualifierIfPossible(true)) return null - return if (isAt(FunctionKeyword)) this else null - } - - // name of declaration - if (!isAt(Identifier)) return null - if (!moveBeforeTypeQualifierIfPossible(false)) return null - - moveBeforeTypeParametersIfPossible() - - takeIf { it.isAt(FunctionKeyword) } - } - } - - /** - * @param leftParenthesis is `(` or `[` - */ - private fun isSimilarToFunctionInvocation(leftParenthesis: SemanticEditorPosition): Boolean = with(leftParenthesis.copy()) { - moveBefore() - - if (!moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment() || isAtEnd) return false - - // calls with types e.g. `test()` - if (isAt(CloseTypeBrace)) { - moveBeforeParentheses(OpenTypeBrace, CloseTypeBrace) - return moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment() && isIdentifier() - } - - return isIdentifier() || isAtAnyOf(RightParenthesis, ArrayClosingBracket) - } - - private fun isDestructuringDeclaration( - leftParenthesis: SemanticEditorPosition, - rightParenthesis: SemanticEditorPosition - ): Boolean = with(leftParenthesis.copy()) { - moveBeforeIgnoringWhiteSpaceOrComment() - - // val (a, b) = 1 to 2 - if (isVarOrVal()) return true - - // in lambda like `val a = { i: Int -> println(i) }` - if (!rightParenthesis.moveBeforeParametersIfPossible()) return false - - return isAt(BlockOpeningBrace) - } - - /** - * @receiver start position of any parameter - */ - private fun SemanticEditorPosition.moveBeforeParametersIfPossible(): Boolean { - while (!isAtEnd) { - if (!moveBeforeParameterIfPossible()) return false - if (!isAt(Comma)) return true - moveBeforeIgnoringWhiteSpaceOrComment() - } - - return false - } - - /** - * @receiver start position of any parameter - */ - private fun SemanticEditorPosition.moveBeforeParameterIfPossible(): Boolean { - // destructuring declaration in lambda parameters without type - // { a, (b, c), d -> - // ^ - if (isAt(RightParenthesis)) return moveBeforeParenthesesIfPossible() - - if (!moveBeforeTypeQualifierIfPossible(true)) return false - - // optional colon - // { a: Int -> - // ^ - // (a: Int) - // ^ - if (isAt(Colon)) { - moveBeforeIgnoringWhiteSpaceOrComment() - - // destructuring declaration in lambda parameters with type - // { a, b, (c, d): Pair, e -> - // ^ - if (isAt(RightParenthesis)) return moveBeforeParenthesesIfPossible() - - // { a: Int -> - // ^ - // (a: Int) - // ^ - if (!isAt(Identifier)) return false - moveBeforeIgnoringWhiteSpaceOrComment() - } - - return true - } - - /*** - * Constructions like `OuterClass/* */.MiddleClass?????`, `String.Companion.testName` - * - * @receiver position of identifier - */ - private fun SemanticEditorPosition.moveBeforeTypeQualifierIfPossible(canStartWithTypeParameter: Boolean): Boolean { - if (!canStartWithTypeParameter && !isAt(Identifier)) return false - - while (!isAtEnd) { - moveBeforeOptionalMix(Quest, *WHITE_SPACE_OR_COMMENT_BIT_SET) - moveBeforeTypeParametersIfPossible() - if (!isAt(Identifier)) return false - moveBeforeIgnoringWhiteSpaceOrComment() - if (!isAt(Dot)) return true - moveBeforeIgnoringWhiteSpaceOrComment() - } - - return false - } - - private fun createAlignMultilineIndent(position: SemanticEditorPosition): Indent { - val beforeLineStart = CharArrayUtil.shiftBackwardUntil(position.chars, position.startOffset, "\n") + 1 - val beforeLineWithoutIndentStart = CharArrayUtil.shiftForward(position.chars, beforeLineStart, " \t") - return Indent.getSpaceIndent(position.startOffset - beforeLineWithoutIndentStart) - } - - private fun SemanticEditorPosition.isWhileInsideDoWhile(): Boolean { - if (!isAt(WhileKeyword)) return false - with(copy()) { - moveBefore() - var whileKeywordLevel = 1 - while (!isAtEnd) when { - isAt(BlockOpeningBrace) -> return false - isAt(DoKeyword) -> { - if (--whileKeywordLevel == 0) return true - moveBefore() - } - isAt(WhileKeyword) -> { - ++whileKeywordLevel - moveBefore() - } - isAt(BlockClosingBrace) -> moveBeforeParentheses(BlockOpeningBrace, BlockClosingBrace) - else -> moveBefore() - } - } - - return false - } - - private fun SemanticEditorPosition.controlFlowStatementBefore(): SemanticEditorPosition? = with(copy()) { - if (isAt(BlockOpeningBrace)) moveBeforeIgnoringWhiteSpaceOrComment() - - if (isControlFlowKeyword()) return this - if (!moveBeforeParenthesesIfPossible()) return null - - return takeIf { isControlFlowKeyword() } - } - - private fun SemanticEditorPosition.isControlFlowKeyword(): Boolean = - currElement in CONTROL_FLOW_KEYWORDS || isCatchKeyword() || isFinallyKeyword() - - private fun SemanticEditorPosition.similarToCatchKeyword(): Boolean = textOfCurrentPosition() == KtTokens.CATCH_KEYWORD.value - - private fun SemanticEditorPosition.isCatchKeyword(): Boolean = with(copy()) { - // try-catch-*-catch - do { - if (!isAt(Identifier)) return false - if (!similarToCatchKeyword()) return false - - moveBeforeIgnoringWhiteSpaceOrComment() - if (!moveBeforeBlockIfPossible()) return false - - if (isAt(TryKeyword)) return true - - if (!moveBeforeParenthesesIfPossible()) return false - } while (!isAtEnd) - - return false - } - - private fun SemanticEditorPosition.isFinallyKeyword(): Boolean { - if (!isAt(Identifier)) return false - if (textOfCurrentPosition() != KtTokens.FINALLY_KEYWORD.value) return false - with(copy()) { - moveBeforeIgnoringWhiteSpaceOrComment() - if (!moveBeforeBlockIfPossible()) return false - - // try-finally - if (isAt(TryKeyword)) return true - - if (!moveBeforeParenthesesIfPossible()) return false - - // try-catch-finally - return isCatchKeyword() - } - } - - private fun SemanticEditorPosition.isIdentifier(): Boolean = isAt(Identifier) || isAt(KtTokens.THIS_KEYWORD) - - private fun SemanticEditorPosition.isVarOrVal(): Boolean = isAtAnyOf(Var, Val) - - private fun SemanticEditorPosition.moveBeforeBlockIfPossible(): Boolean = moveBeforeParenthesesIfPossible( - leftParenthesis = BlockOpeningBrace, - rightParenthesis = BlockClosingBrace, - ) - - private fun SemanticEditorPosition.moveBeforeTypeParametersIfPossible(): Boolean = moveBeforeParenthesesIfPossible( - leftParenthesis = OpenTypeBrace, - rightParenthesis = CloseTypeBrace, - ) - - private fun SemanticEditorPosition.moveBeforeParenthesesIfPossible(): Boolean = moveBeforeParenthesesIfPossible( - leftParenthesis = LeftParenthesis, - rightParenthesis = RightParenthesis, - ) - - private fun SemanticEditorPosition.moveBeforeParenthesesIfPossible( - leftParenthesis: SemanticEditorPosition.SyntaxElement, - rightParenthesis: SemanticEditorPosition.SyntaxElement, - ): Boolean { - if (!isAt(rightParenthesis)) return false - - moveBeforeParentheses(leftParenthesis, rightParenthesis) - moveBeforeWhileThisIsWhiteSpaceOrComment() - return true - } - - private fun SemanticEditorPosition.moveBeforeWhileThisIsWhiteSpaceOrComment() = moveBeforeOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET) - - private fun SemanticEditorPosition.moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment(): Boolean { - while (!isAtEnd) { - if (isAt(Whitespace) && isAtMultiline) return false - if (!isAt(BlockComment)) break - moveBefore() - } - - return true - } - - private fun SemanticEditorPosition.moveBeforeIgnoringWhiteSpaceOrComment() { - moveBefore() - moveBeforeWhileThisIsWhiteSpaceOrComment() - } - - private fun SemanticEditorPosition.beforeIgnoringWhiteSpaceOrComment() = copyAnd { it.moveBeforeIgnoringWhiteSpaceOrComment() } - private enum class KotlinElement : SemanticEditorPosition.SyntaxElement { TemplateEntryOpen, TemplateEntryClose, @@ -552,16 +151,26 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider companion object { private val SYNTAX_MAP: Map = hashMapOf( KtTokens.WHITE_SPACE to Whitespace, - KtTokens.LONG_TEMPLATE_ENTRY_START to TemplateEntryOpen, - KtTokens.LONG_TEMPLATE_ENTRY_END to TemplateEntryClose, KtTokens.EOL_COMMENT to LineComment, KtTokens.BLOCK_COMMENT to BlockComment, KtTokens.DOC_COMMENT to KDoc, KtTokens.ARROW to Arrow, + + KtTokens.LONG_TEMPLATE_ENTRY_START to TemplateEntryOpen, + KtTokens.LONG_TEMPLATE_ENTRY_END to TemplateEntryClose, + KtTokens.LBRACE to BlockOpeningBrace, KtTokens.RBRACE to BlockClosingBrace, + KtTokens.LPAR to LeftParenthesis, KtTokens.RPAR to RightParenthesis, + + KtTokens.LBRACKET to ArrayOpeningBracket, + KtTokens.RBRACKET to ArrayClosingBracket, + + KtTokens.LT to OpenTypeBrace, + KtTokens.GT to CloseTypeBrace, + KtTokens.IF_KEYWORD to IfKeyword, KtTokens.ELSE_KEYWORD to ElseKeyword, KtTokens.WHEN_KEYWORD to WhenKeyword, @@ -569,12 +178,9 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider KtTokens.WHILE_KEYWORD to WhileKeyword, KtTokens.DO_KEYWORD to DoKeyword, KtTokens.FOR_KEYWORD to ForKeyword, + KtTokens.REGULAR_STRING_PART to RegularStringPart, - KtTokens.LBRACKET to ArrayOpeningBracket, - KtTokens.RBRACKET to ArrayClosingBracket, KtTokens.IDENTIFIER to Identifier, - KtTokens.LT to OpenTypeBrace, - KtTokens.GT to CloseTypeBrace, KtTokens.FUN_KEYWORD to FunctionKeyword, KtTokens.DOT to Dot, KtTokens.QUEST to Quest, @@ -602,6 +208,405 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider LineComment, BlockComment, ) + + private fun IndentCalculatorFactory.createIndentCalculatorForBrace( + before: SemanticEditorPosition, + after: SemanticEditorPosition, + leftBraceType: SemanticEditorPosition.SyntaxElement, + rightBraceType: SemanticEditorPosition.SyntaxElement + ): IndentCalculator? { + val leftBrace = before.copyAnd { + it.moveToLeftParenthesisBackwardsSkippingNested(leftBraceType, rightBraceType) + } + + val indent = if (after.after().afterOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET).isAt(Comma)) + createAlignMultilineIndent(leftBrace) + else + Indent.getNoneIndent() + + return createIndentCalculator(indent, leftBrace.startOffset) + } + + private fun IndentCalculatorFactory.createIndentCalculatorForParenthesis( + leftParenthesis: SemanticEditorPosition, + currentPosition: SemanticEditorPosition, + rightParenthesis: SemanticEditorPosition, + offset: Int, + settings: KotlinIndentationAdjuster, + ): IndentCalculator? { + assert(leftParenthesis.isAt(LeftParenthesis)) + assert(rightParenthesis.isAt(RightParenthesis)) + + // case only for caret before [RightParenthesis] + if (!currentPosition.hasLineBreaksAfter(offset)) { + val indentForParentheses by lazy { + if (settings.alignWhenMultilineFunctionParentheses) createAlignMultilineIndent(leftParenthesis) else Indent.getNoneIndent() + } + + findFunctionKeywordBeforeIdentifier(leftParenthesis.beforeIgnoringWhiteSpaceOrComment())?.let { + return createIndentCalculator(indentForParentheses, it.startOffset) + } + + // NB: this covered [KtTokens.CONSTRUCTOR_KEYWORD], [KtTokens.SET_KEYWORD], [KtTokens.GET_KEYWORD], [KtTokens.INIT_KEYWORD] as well + if (isSimilarToFunctionInvocation(leftParenthesis)) { + return createIndentCalculator(indentForParentheses, leftParenthesis.startOffset) + } + + if (isDestructuringDeclaration(leftParenthesis, rightParenthesis)) { + return createIndentCalculator(Indent.getNoneIndent(), leftParenthesis.startOffset) + } + + leftParenthesis.beforeIgnoringWhiteSpaceOrComment().let { keyword -> + if (keyword.isControlFlowKeyword()) { + return createIndentCalculator(Indent.getNoneIndent(), keyword.startOffset) + } + } + + val indentForBinaryExpression = if (settings.alignWhenMultilineBinaryExpression) + createAlignMultilineIndent(leftParenthesis) + else + Indent.getContinuationIndent() + + return createIndentCalculator(indentForBinaryExpression, leftParenthesis.startOffset) + } + + return null + } + + /** + * @receiver is position before '=' for expression body or '{' for block body + */ + private fun SemanticEditorPosition.isFunctionDeclaration(): Boolean = findFunctionDeclarationBeforeBody(this) != null + + /** + * @param endOfDeclaration is position before '=' for expression body or '{' for block body + */ + private fun findFunctionOrPropertyOrMultiDeclarationBefore(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? = + findFunctionDeclarationBeforeBody(endOfDeclaration) + ?: findPropertyDeclarationBeforeAssignment(endOfDeclaration) + ?: findMultiDeclarationBeforeAssignment(endOfDeclaration) + + /** + * `val (a, b) = 1 to 2` + * ^ + */ + private fun findMultiDeclarationBeforeAssignment(rightParenthesis: SemanticEditorPosition): SemanticEditorPosition? { + if (!rightParenthesis.isAt(RightParenthesis)) return null + return with(rightParenthesis.copy()) { + if (!moveBeforeParenthesesIfPossible()) return null + takeIf { isVarOrVal() } + } + } + + /** + * `val a = 5` + * `val a: Int = 5` + * `val List.a: Int = 5` + * `val List.a: Int = 5` + */ + private fun findPropertyDeclarationBeforeAssignment(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? { + // `val a = 5` + // this is a false positive for a declaration with explicit return type + if (endOfDeclaration.isAt(Identifier)) { + findPropertyKeywordBeforeIdentifier(endOfDeclaration)?.let { return it } + } + + return with(endOfDeclaration.copy()) { + // explicit type `fun a(): String` or `val a: String` + if (moveBeforeTypeQualifierIfPossible(true)) { + if (!isAt(Colon)) return null + moveBeforeIgnoringWhiteSpaceOrComment() + } + + findPropertyKeywordBeforeIdentifier(this) + } + } + + /** + * `val a = fun() { }` + * `val a = fun String.Companion????.() { }` + * `fun a() = 4` + * `fun a(): Int = 4` + * `fun Int.a(): Int = 4` + * `fun T.a(): Int = 4` + */ + private fun findFunctionDeclarationBeforeBody(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? = + with(endOfDeclaration.copy()) { + // explicit type `fun a(): String` + // ^ + if (moveBeforeTypeQualifierIfPossible(true)) { + if (!isAt(Colon)) return null + moveBeforeIgnoringWhiteSpaceOrComment() + } + + if (!moveBeforeParenthesesIfPossible()) return null + + findFunctionKeywordBeforeIdentifier(this) + } + + private fun findPropertyKeywordBeforeIdentifier(identifierPosition: SemanticEditorPosition): SemanticEditorPosition? { + if (!identifierPosition.isAt(Identifier)) return null + return with(identifierPosition.copy()) { + if (!moveBeforeTypeQualifierIfPossible(false)) return null + // `val List.prop` + moveBeforeTypeParametersIfPossible() + takeIf { it.isVarOrVal() } + } + } + + /** + * @return position of `fun` keyword before the declaration or null + * TODO: support [KtTokens.CONSTRUCTOR_KEYWORD], [KtTokens.INIT_KEYWORD]. Maybe [KtTokens.SET_KEYWORD], [KtTokens.GET_KEYWORD] (related to KT-39444) + */ + private fun findFunctionKeywordBeforeIdentifier(identifierPosition: SemanticEditorPosition): SemanticEditorPosition? { + // anonymous function `val a = fun() { }` + // ^ + if (identifierPosition.isAt(FunctionKeyword)) return identifierPosition + + return with(identifierPosition.copy()) { + moveBeforeWhileThisIsWhiteSpaceOrComment() + + // anonymous function with receiver `val a = fun String.Companion????.() { }` + // ^ + if (isAt(Dot)) { + moveBeforeIgnoringWhiteSpaceOrComment() + if (!moveBeforeTypeQualifierIfPossible(true)) return null + return if (isAt(FunctionKeyword)) this else null + } + + // name of declaration + if (!isAt(Identifier)) return null + if (!moveBeforeTypeQualifierIfPossible(false)) return null + + moveBeforeTypeParametersIfPossible() + + takeIf { it.isAt(FunctionKeyword) } + } + } + + /** + * @param leftParenthesis is `(` or `[` + */ + private fun isSimilarToFunctionInvocation(leftParenthesis: SemanticEditorPosition): Boolean = with(leftParenthesis.copy()) { + moveBefore() + + if (!moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment() || isAtEnd) return false + + // calls with types e.g. `test()` + if (isAt(CloseTypeBrace)) { + moveBeforeParentheses(OpenTypeBrace, CloseTypeBrace) + return moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment() && isIdentifier() + } + + return isIdentifier() || isAtAnyOf(RightParenthesis, ArrayClosingBracket) + } + + private fun isDestructuringDeclaration( + leftParenthesis: SemanticEditorPosition, + rightParenthesis: SemanticEditorPosition + ): Boolean = with(leftParenthesis.copy()) { + moveBeforeIgnoringWhiteSpaceOrComment() + + // val (a, b) = 1 to 2 + if (isVarOrVal()) return true + + // in lambda like `val a = { i: Int -> println(i) }` + if (!rightParenthesis.moveBeforeParametersIfPossible()) return false + + return isAt(BlockOpeningBrace) + } + + /** + * @receiver start position of any parameter + */ + private fun SemanticEditorPosition.moveBeforeParametersIfPossible(): Boolean { + while (!isAtEnd) { + if (!moveBeforeParameterIfPossible()) return false + if (!isAt(Comma)) return true + moveBeforeIgnoringWhiteSpaceOrComment() + } + + return false + } + + /** + * @receiver start position of any parameter + */ + private fun SemanticEditorPosition.moveBeforeParameterIfPossible(): Boolean { + // destructuring declaration in lambda parameters without type + // { a, (b, c), d -> + // ^ + if (isAt(RightParenthesis)) return moveBeforeParenthesesIfPossible() + + if (!moveBeforeTypeQualifierIfPossible(true)) return false + + // optional colon + // { a: Int -> + // ^ + // (a: Int) + // ^ + if (isAt(Colon)) { + moveBeforeIgnoringWhiteSpaceOrComment() + + // destructuring declaration in lambda parameters with type + // { a, b, (c, d): Pair, e -> + // ^ + if (isAt(RightParenthesis)) return moveBeforeParenthesesIfPossible() + + // { a: Int -> + // ^ + // (a: Int) + // ^ + if (!isAt(Identifier)) return false + moveBeforeIgnoringWhiteSpaceOrComment() + } + + return true + } + + /*** + * Constructions like `OuterClass/* */.MiddleClass?????`, `String.Companion.testName` + * + * @receiver position of identifier + */ + private fun SemanticEditorPosition.moveBeforeTypeQualifierIfPossible(canStartWithTypeParameter: Boolean): Boolean { + if (!canStartWithTypeParameter && !isAt(Identifier)) return false + + while (!isAtEnd) { + moveBeforeOptionalMix(Quest, *WHITE_SPACE_OR_COMMENT_BIT_SET) + moveBeforeTypeParametersIfPossible() + if (!isAt(Identifier)) return false + moveBeforeIgnoringWhiteSpaceOrComment() + if (!isAt(Dot)) return true + moveBeforeIgnoringWhiteSpaceOrComment() + } + + return false + } + + private fun createAlignMultilineIndent(position: SemanticEditorPosition): Indent { + val beforeLineStart = CharArrayUtil.shiftBackwardUntil(position.chars, position.startOffset, "\n") + 1 + val beforeLineWithoutIndentStart = CharArrayUtil.shiftForward(position.chars, beforeLineStart, " \t") + return Indent.getSpaceIndent(position.startOffset - beforeLineWithoutIndentStart) + } + + private fun SemanticEditorPosition.isWhileInsideDoWhile(): Boolean { + if (!isAt(WhileKeyword)) return false + with(copy()) { + moveBefore() + var whileKeywordLevel = 1 + while (!isAtEnd) when { + isAt(BlockOpeningBrace) -> return false + isAt(DoKeyword) -> { + if (--whileKeywordLevel == 0) return true + moveBefore() + } + isAt(WhileKeyword) -> { + ++whileKeywordLevel + moveBefore() + } + isAt(BlockClosingBrace) -> moveBeforeParentheses(BlockOpeningBrace, BlockClosingBrace) + else -> moveBefore() + } + } + + return false + } + + private fun SemanticEditorPosition.controlFlowStatementBefore(): SemanticEditorPosition? = with(copy()) { + if (isAt(BlockOpeningBrace)) moveBeforeIgnoringWhiteSpaceOrComment() + + if (isControlFlowKeyword()) return this + if (!moveBeforeParenthesesIfPossible()) return null + + return takeIf { isControlFlowKeyword() } + } + + private fun SemanticEditorPosition.isControlFlowKeyword(): Boolean = + currElement in CONTROL_FLOW_KEYWORDS || isCatchKeyword() || isFinallyKeyword() + + private fun SemanticEditorPosition.similarToCatchKeyword(): Boolean = textOfCurrentPosition() == KtTokens.CATCH_KEYWORD.value + + private fun SemanticEditorPosition.isCatchKeyword(): Boolean = with(copy()) { + // try-catch-*-catch + do { + if (!isAt(Identifier)) return false + if (!similarToCatchKeyword()) return false + + moveBeforeIgnoringWhiteSpaceOrComment() + if (!moveBeforeBlockIfPossible()) return false + + if (isAt(TryKeyword)) return true + + if (!moveBeforeParenthesesIfPossible()) return false + } while (!isAtEnd) + + return false + } + + private fun SemanticEditorPosition.isFinallyKeyword(): Boolean { + if (!isAt(Identifier)) return false + if (textOfCurrentPosition() != KtTokens.FINALLY_KEYWORD.value) return false + with(copy()) { + moveBeforeIgnoringWhiteSpaceOrComment() + if (!moveBeforeBlockIfPossible()) return false + + // try-finally + if (isAt(TryKeyword)) return true + + if (!moveBeforeParenthesesIfPossible()) return false + + // try-catch-finally + return isCatchKeyword() + } + } + + private fun SemanticEditorPosition.moveBeforeWhileThisIsWhiteSpaceOrComment() = + moveBeforeOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET) + + private fun SemanticEditorPosition.moveBeforeIgnoringWhiteSpaceOrComment() { + moveBefore() + moveBeforeWhileThisIsWhiteSpaceOrComment() + } + + private fun SemanticEditorPosition.beforeIgnoringWhiteSpaceOrComment() = copyAnd { it.moveBeforeIgnoringWhiteSpaceOrComment() } + private fun SemanticEditorPosition.moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment(): Boolean { + while (!isAtEnd) { + if (isAt(Whitespace) && isAtMultiline) return false + if (!isAt(BlockComment)) break + moveBefore() + } + + return true + } + + private fun SemanticEditorPosition.isIdentifier(): Boolean = isAt(Identifier) || isAt(KtTokens.THIS_KEYWORD) + private fun SemanticEditorPosition.isVarOrVal(): Boolean = isAtAnyOf(Var, Val) + private fun SemanticEditorPosition.moveBeforeBlockIfPossible(): Boolean = moveBeforeParenthesesIfPossible( + leftParenthesis = BlockOpeningBrace, + rightParenthesis = BlockClosingBrace, + ) + + private fun SemanticEditorPosition.moveBeforeTypeParametersIfPossible(): Boolean = moveBeforeParenthesesIfPossible( + leftParenthesis = OpenTypeBrace, + rightParenthesis = CloseTypeBrace, + ) + + private fun SemanticEditorPosition.moveBeforeParenthesesIfPossible(): Boolean = moveBeforeParenthesesIfPossible( + leftParenthesis = LeftParenthesis, + rightParenthesis = RightParenthesis, + ) + + private fun SemanticEditorPosition.moveBeforeParenthesesIfPossible( + leftParenthesis: SemanticEditorPosition.SyntaxElement, + rightParenthesis: SemanticEditorPosition.SyntaxElement, + ): Boolean { + if (!isAt(rightParenthesis)) return false + + moveBeforeParentheses(leftParenthesis, rightParenthesis) + moveBeforeWhileThisIsWhiteSpaceOrComment() + return true + } } }