diff --git a/plugins/kotlin/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt b/plugins/kotlin/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt index 3bb0174edde0..acc6a1a93a75 100644 --- a/plugins/kotlin/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt +++ b/plugins/kotlin/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt @@ -35,8 +35,10 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.children +import org.jetbrains.kotlin.psi.psiUtil.leaves import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.siblings +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes private val QUALIFIED_OPERATION = TokenSet.create(DOT, SAFE_ACCESS) private val QUALIFIED_EXPRESSIONS = TokenSet.create(KtNodeTypes.DOT_QUALIFIED_EXPRESSION, KtNodeTypes.SAFE_ACCESS_EXPRESSION) @@ -118,11 +120,13 @@ abstract class KotlinCommonBlock( // Create fake ".something" or "?.something" block here, so child indentation will be // relative to it when it starts from new line (see Indent javadoc). - val indent = if (settings.kotlinCustomSettings.CONTINUATION_INDENT_FOR_CHAINED_CALLS) - Indent.getContinuationWithoutFirstIndent() + val indentType = if (settings.kotlinCustomSettings.CONTINUATION_INDENT_FOR_CHAINED_CALLS) + Indent.Type.CONTINUATION else - Indent.getNormalIndent() + Indent.Type.NORMAL val isNonFirstChainedCall = operationBlockIndex > 0 && isCallBlock(nodeSubBlocks[operationBlockIndex - 1]) + val indent = Indent.getIndent(indentType, false, + isNonFirstChainedCall && hasLineBreakBefore(nodeSubBlocks[operationBlockIndex - 1])) val wrap = if ((settings.kotlinCommonSettings.WRAP_FIRST_METHOD_IN_CALL_CHAIN || isNonFirstChainedCall) && canWrapCallChain(node)) Wrap.createWrap(settings.kotlinCommonSettings.METHOD_CALL_CHAIN_WRAP, true) @@ -545,6 +549,21 @@ fun needWrapArgumentList(psi: PsiElement): Boolean { return args?.singleOrNull()?.getArgumentExpression() !is KtObjectLiteralExpression } +private fun hasLineBreakBefore(block: ASTBlock): Boolean { + val topLevelParent = block.node.parents().firstOrNull { + it.treeParent.elementType == KtNodeTypes.BLOCK || it.treeParent.elementType == KtStubElementTypes.FILE + } ?: return false + for (leaf in block.node.leaves(forward = false)) { + if (leaf.textContains('\n')) { + return true + } + if (leaf.textRange.startOffset == topLevelParent.startOffset) { + break + } + } + return false +} + fun NodeIndentStrategy.PositionStrategy.continuationIf( option: (KotlinCodeStyleSettings) -> Boolean, indentFirst: Boolean = false diff --git a/plugins/kotlin/idea/tests/testData/formatter/KT15099.after.kt b/plugins/kotlin/idea/tests/testData/formatter/KT15099.after.kt new file mode 100644 index 000000000000..15548e8c4487 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/formatter/KT15099.after.kt @@ -0,0 +1,8 @@ +fun test() { + Single.just(Object()) + .map { + it + }.map { + it // The code unexpectedly shifts to the left + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/formatter/KT15099.kt b/plugins/kotlin/idea/tests/testData/formatter/KT15099.kt new file mode 100644 index 000000000000..d90aaae61fb0 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/formatter/KT15099.kt @@ -0,0 +1,8 @@ +fun test() { + Single.just(Object()) + .map { + it + }.map { + it // The code unexpectedly shifts to the left + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/formatter/KT20362.after.kt b/plugins/kotlin/idea/tests/testData/formatter/KT20362.after.kt index 580e03550b96..5deca704a2b4 100644 --- a/plugins/kotlin/idea/tests/testData/formatter/KT20362.after.kt +++ b/plugins/kotlin/idea/tests/testData/formatter/KT20362.after.kt @@ -1,6 +1,6 @@ val ret = (DB.Article innerJoin DB.Project).slice(DB.Article.project, DB.Article.project.count()).select { - filterBlogs(params) - }.groupBy(DB.Article.project).map { - val count = it[DB.Article.project.count()] - val project = it[DB.Article.team.count()] - } \ No newline at end of file + filterBlogs(params) +}.groupBy(DB.Article.project).map { + val count = it[DB.Article.project.count()] + val project = it[DB.Article.team.count()] +} \ No newline at end of file