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 27668caa22d..5dcde973091 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 @@ -71,6 +71,9 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider before.isAt(BlockOpeningBrace) && after.isAt(BlockClosingBrace) && !currentPosition.hasLineBreaksAfter(offset) -> return factory.createIndentCalculator(Indent.getNoneIndent(), before.startOffset) + before.isAt(BlockOpeningBrace) && before.beforeIgnoringWhiteSpaceOrComment().isFunctionDeclaration() -> + return factory.createIndentCalculator(Indent.getNormalIndent(), before.startOffset) + after.isAt(Quest) && after.after().isAt(Colon) -> { val indent = if (settings.continuationIndentInElvis) Indent.getContinuationIndent() @@ -102,7 +105,7 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider } before.isAt(Eq) -> { - val declaration = findFunctionOrPropertyDeclarationBefore(before.copyAnd { it.moveBeforeIgnoringWhiteSpaceOrComment() }) + val declaration = findFunctionOrPropertyOrMultiDeclarationBefore(before.beforeIgnoringWhiteSpaceOrComment()) if (declaration != null) { val indent = if (settings.continuationIndentForExpressionBodies) Indent.getContinuationIndent() @@ -117,7 +120,7 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider factory.createIndentCalculatorForParenthesis(before, currentPosition, after, offset, settings)?.let { return it } } - findFunctionOrPropertyDeclarationBefore(before)?.let { + findFunctionOrPropertyOrMultiDeclarationBefore(before)?.let { return factory.createIndentCalculator(Indent.getNoneIndent(), it.startOffset) } @@ -149,7 +152,7 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider if (settings.alignWhenMultilineFunctionParentheses) createAlignMultilineIndent(leftParenthesis) else Indent.getNoneIndent() } - findFunctionKeywordBeforeIdentifier(leftParenthesis.copyAnd { it.moveBeforeIgnoringWhiteSpaceOrComment() })?.let { + findFunctionKeywordBeforeIdentifier(leftParenthesis.beforeIgnoringWhiteSpaceOrComment())?.let { return createIndentCalculator(indentForParentheses, it.startOffset) } @@ -162,7 +165,7 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider return createIndentCalculator(Indent.getNoneIndent(), leftParenthesis.startOffset) } - leftParenthesis.copyAnd { it.moveBeforeIgnoringWhiteSpaceOrComment() }.let { keyword -> + leftParenthesis.beforeIgnoringWhiteSpaceOrComment().let { keyword -> if (keyword.isControlFlowKeyword()) { return createIndentCalculator(Indent.getNoneIndent(), keyword.startOffset) } @@ -180,36 +183,76 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider } /** - * @param declarationPosition is position before '=' for expression body or '{' + * @receiver is position before '=' for expression body or '{' for block body */ - private fun findFunctionOrPropertyDeclarationBefore(declarationPosition: SemanticEditorPosition): SemanticEditorPosition? { + 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 false positive for declaration with explicit return type - if (declarationPosition.isAt(Identifier)) { - findPropertyKeywordBeforeIdentifier(declarationPosition)?.let { return it } + // this is a false positive for a declaration with explicit return type + if (endOfDeclaration.isAt(Identifier)) { + findPropertyKeywordBeforeIdentifier(endOfDeclaration)?.let { return it } } - return with(declarationPosition.copy()) { + return with(endOfDeclaration.copy()) { // explicit type `fun a(): String` or `val a: String` if (moveBeforeTypeQualifierIfPossible(true)) { if (!isAt(Colon)) return null moveBeforeIgnoringWhiteSpaceOrComment() } - if (isAt(RightParenthesis)) { - if (!moveBeforeParenthesesIfPossible()) return null - - // destructuring declaration `val (a, b)` - if (isVarOrVal()) - this - else - findFunctionKeywordBeforeIdentifier(this) - } else { - findPropertyKeywordBeforeIdentifier(this) - } + 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()) { @@ -226,12 +269,14 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider */ 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 @@ -453,6 +498,8 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider moveBeforeWhileThisIsWhiteSpaceOrComment() } + private fun SemanticEditorPosition.beforeIgnoringWhiteSpaceOrComment() = copyAnd { it.moveBeforeIgnoringWhiteSpaceOrComment() } + private enum class KotlinElement : SemanticEditorPosition.SyntaxElement { TemplateEntryOpen, TemplateEntryClose, diff --git a/idea/testData/indentationOnNewline/KT20783.after.kt b/idea/testData/indentationOnNewline/KT20783.after.kt index 033d6324f77..3482ce62572 100644 --- a/idea/testData/indentationOnNewline/KT20783.after.kt +++ b/idea/testData/indentationOnNewline/KT20783.after.kt @@ -1,8 +1,8 @@ object Test { // TODO restore correct behavior // some fun test() { - + } } -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file +// IGNORE_FORMATTER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/KT20783.kt b/idea/testData/indentationOnNewline/KT20783.kt index 3bd9ba14ccf..0482bf977f1 100644 --- a/idea/testData/indentationOnNewline/KT20783.kt +++ b/idea/testData/indentationOnNewline/KT20783.kt @@ -4,4 +4,4 @@ object Test { // TODO restore correct behavior } } -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file +// IGNORE_FORMATTER \ No newline at end of file