diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt index 2bc49676cd9..b974898f620 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt @@ -20,6 +20,7 @@ import com.intellij.injected.editor.VirtualFileWindow import com.intellij.lang.ASTNode import com.intellij.openapi.util.TextRange import com.intellij.psi.* +import com.intellij.psi.impl.source.tree.TreeUtil import com.intellij.psi.search.PsiSearchScopeUtil import com.intellij.psi.search.SearchScope import com.intellij.psi.util.PsiTreeUtil @@ -400,3 +401,11 @@ fun ASTNode.siblings(forward: Boolean = true): Sequence { return generateSequence(treePrev) { it.treePrev } } } + +fun ASTNode.leaves(forward: Boolean = true): Sequence { + if (forward) { + return generateSequence(TreeUtil.nextLeaf(this)) { TreeUtil.nextLeaf(it) } + } else { + return generateSequence(TreeUtil.prevLeaf(this)) { TreeUtil.prevLeaf(it) } + } +} diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt index 3bb0174edde..acc6a1a93a7 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt +++ b/idea/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/idea/testData/formatter/KT15099.after.kt b/idea/testData/formatter/KT15099.after.kt new file mode 100644 index 00000000000..15548e8c448 --- /dev/null +++ b/idea/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/idea/testData/formatter/KT15099.kt b/idea/testData/formatter/KT15099.kt new file mode 100644 index 00000000000..d90aaae61fb --- /dev/null +++ b/idea/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/idea/testData/formatter/KT20362.after.kt b/idea/testData/formatter/KT20362.after.kt index 580e03550b9..5deca704a2b 100644 --- a/idea/testData/formatter/KT20362.after.kt +++ b/idea/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