diff --git a/idea/src/org/jetbrains/jet/plugin/formatter/JetBlock.java b/idea/src/org/jetbrains/jet/plugin/formatter/JetBlock.java index a0a0fb2c723..1992a8e94d0 100644 --- a/idea/src/org/jetbrains/jet/plugin/formatter/JetBlock.java +++ b/idea/src/org/jetbrains/jet/plugin/formatter/JetBlock.java @@ -19,12 +19,14 @@ package org.jetbrains.jet.plugin.formatter; import com.intellij.formatting.*; import com.intellij.formatting.alignment.AlignmentStrategy; import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.Condition; import com.intellij.psi.TokenType; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.formatter.common.AbstractBlock; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.kdoc.lexer.KDocTokens; @@ -51,6 +53,7 @@ public class JetBlock extends AbstractBlock { private List mySubBlocks; + private static final TokenSet QUALIFIED_OPERATION = TokenSet.create(DOT, SAFE_ACCESS); private static final TokenSet CODE_BLOCKS = TokenSet.create( BLOCK, CLASS_BODY, @@ -81,9 +84,30 @@ public class JetBlock extends AbstractBlock { @Override protected List buildChildren() { if (mySubBlocks == null) { - mySubBlocks = buildSubBlocks(); + List nodeSubBlocks = buildSubBlocks(); + + if (getNode().getElementType() == DOT_QUALIFIED_EXPRESSION || getNode().getElementType() == SAFE_ACCESS_EXPRESSION) { + int operationBlockIndex = findNodeBlockIndex(nodeSubBlocks, QUALIFIED_OPERATION); + if (operationBlockIndex != -1) { + // Create fake ".something" or "?.something" block here, so child indentation will be + // relative to it when it starts from new line (see Indent javadoc). + + Block operationBlock = nodeSubBlocks.get(operationBlockIndex); + SyntheticKotlinBlock operationSynteticBlock = + new SyntheticKotlinBlock( + ((ASTBlock) operationBlock).getNode(), + nodeSubBlocks.subList(operationBlockIndex, nodeSubBlocks.size()), + null, operationBlock.getIndent(), null, mySpacingBuilder); + + nodeSubBlocks = ContainerUtil.addAll( + ContainerUtil.newArrayList(nodeSubBlocks.subList(0, operationBlockIndex)), + operationSynteticBlock); + } + } + + mySubBlocks = nodeSubBlocks; } - return new ArrayList(mySubBlocks); + return mySubBlocks; } private List buildSubBlocks() { @@ -102,6 +126,7 @@ public class JetBlock extends AbstractBlock { blocks.add(buildSubBlock(child, childrenAlignmentStrategy)); } + return Collections.unmodifiableList(blocks); } @@ -288,7 +313,7 @@ public class JetBlock extends AbstractBlock { strategy("Indent for block content") .in(BLOCK, CLASS_BODY, FUNCTION_LITERAL) .notForType(RBRACE, LBRACE, BLOCK) - .set(Indent.getNormalIndent()), + .set(Indent.getNormalIndent(false)), strategy("Indent for property accessors") .in(PROPERTY) @@ -316,7 +341,7 @@ public class JetBlock extends AbstractBlock { strategy("Chained calls") .in(DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION) - .set(Indent.getContinuationWithoutFirstIndent(true)), + .set(Indent.getContinuationWithoutFirstIndent(false)), strategy("Delegation list") .in(DELEGATION_SPECIFIER_LIST, INITIALIZER_LIST) @@ -397,4 +422,16 @@ public class JetBlock extends AbstractBlock { } return defaultAlignment; } + + private static int findNodeBlockIndex(List blocks, final TokenSet tokenSet) { + return ContainerUtil.indexOf(blocks, new Condition() { + @Override + public boolean value(Block block) { + if (!(block instanceof ASTBlock)) return false; + + ASTNode node = ((ASTBlock) block).getNode(); + return node != null && tokenSet.contains(node.getElementType()); + } + }); + } } diff --git a/idea/src/org/jetbrains/jet/plugin/formatter/KotlinSpacingBuilder.kt b/idea/src/org/jetbrains/jet/plugin/formatter/KotlinSpacingBuilder.kt index dc09a03f712..54570596bdb 100644 --- a/idea/src/org/jetbrains/jet/plugin/formatter/KotlinSpacingBuilder.kt +++ b/idea/src/org/jetbrains/jet/plugin/formatter/KotlinSpacingBuilder.kt @@ -22,9 +22,15 @@ import com.intellij.psi.codeStyle.CodeStyleSettings import java.util.ArrayList import com.intellij.psi.tree.IElementType import org.jetbrains.jet.plugin.JetLanguage - +import com.intellij.psi.formatter.common.AbstractBlock +import com.intellij.lang.ASTNode class KotlinSpacingBuilder(val codeStyleSettings: CodeStyleSettings) { + class SpacingNodeBlock(node: ASTNode): AbstractBlock(node, null, null) { + override fun buildChildren(): MutableList? = ArrayList() + override fun getSpacing(child1: Block?, child2: Block): Spacing? = null + override fun isLeaf(): Boolean = false + } private val builders = ArrayList() diff --git a/idea/src/org/jetbrains/jet/plugin/formatter/SynteticKotlinBlock.kt b/idea/src/org/jetbrains/jet/plugin/formatter/SynteticKotlinBlock.kt new file mode 100644 index 00000000000..c4aab565905 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/formatter/SynteticKotlinBlock.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.formatter + +import com.intellij.formatting.Block +import com.intellij.formatting.Alignment +import com.intellij.formatting.Indent +import com.intellij.formatting.Wrap +import com.intellij.openapi.util.TextRange +import com.intellij.formatting.ChildAttributes +import com.intellij.formatting.Spacing +import com.intellij.lang.ASTNode +import com.intellij.psi.formatter.common.AbstractBlock +import com.intellij.formatting.ASTBlock +import java.util.ArrayList + +public class SyntheticKotlinBlock( + private val _node: ASTNode, + private val _subBlocks: MutableList, + private val _alignment: Alignment?, + private val _indent: Indent?, + private val _wrap: Wrap?, + private val spacingBuilder: KotlinSpacingBuilder) : ASTBlock { + + private val _textRange = TextRange( + _subBlocks.first().getTextRange().getStartOffset(), + _subBlocks.last().getTextRange().getEndOffset()) + + override fun getTextRange(): TextRange = _textRange + override fun getSubBlocks() = _subBlocks + override fun getWrap() = _wrap + override fun getIndent() = _indent + override fun getAlignment() = _alignment + override fun getChildAttributes(newChildIndex: Int) = ChildAttributes(getIndent(), null) + override fun isIncomplete() = getSubBlocks().last().isIncomplete() + override fun isLeaf() = false + override fun getNode() = _node + override fun getSpacing(child1: Block?, child2: Block): Spacing? = + spacingBuilder.getSpacing(KotlinSpacingBuilder.SpacingNodeBlock(_node.getTreeParent()!!), child1, child2) + + + override fun toString(): String { + var child = _subBlocks.first() + var treeNode: ASTNode? = null + while (treeNode == null) when (child) { + is AbstractBlock -> { + treeNode = (child as AbstractBlock).getNode() + } + is SyntheticKotlinBlock -> { + child = (child as SyntheticKotlinBlock).getSubBlocks().first() + } + else -> break + } + + val textRange = getTextRange() + if (treeNode != null) { + val psi = treeNode!!.getPsi() + if (psi != null) { + val file = psi.getContainingFile() + if (file != null) { + return file.getText()!!.subSequence(textRange.getStartOffset(), textRange.getEndOffset()).toString() + " " + textRange + } + } + } + + return getClass().getName() + ": " + textRange + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/formatter/kotlinSpacingRules.kt b/idea/src/org/jetbrains/jet/plugin/formatter/kotlinSpacingRules.kt index f5559539829..bdb9f9fa07d 100644 --- a/idea/src/org/jetbrains/jet/plugin/formatter/kotlinSpacingRules.kt +++ b/idea/src/org/jetbrains/jet/plugin/formatter/kotlinSpacingRules.kt @@ -88,6 +88,9 @@ fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder { betweenInside(DOT, IDENTIFIER, FUN).spacing(0, 0, 0, false, 0) afterInside(IDENTIFIER, FUN).spacing(0, 0, 0, false, 0) + aroundInside(DOT, DOT_QUALIFIED_EXPRESSION).spaces(0) + aroundInside(SAFE_ACCESS, SAFE_ACCESS_EXPRESSION).spaces(0) + between(MODIFIERS_LIST_ENTRIES, MODIFIERS_LIST_ENTRIES).spaces(1) after(LBRACKET).spaces(0) diff --git a/idea/testData/formatter/FunctionLiteralsInChainCalls.after.kt b/idea/testData/formatter/FunctionLiteralsInChainCalls.after.kt new file mode 100644 index 00000000000..3b29c05c5f0 --- /dev/null +++ b/idea/testData/formatter/FunctionLiteralsInChainCalls.after.kt @@ -0,0 +1,45 @@ +import java.util.ArrayList + +fun test() { + val abc = ArrayList() + .map { + it * 2 + } + .filter { + it > 4 + } +} + +fun test1() { + val abc = ArrayList() + .map({ + it * 2 + }) + .filter({ + it > 4 + }) +} + +fun test2() { + val abc = ArrayList() + .map { + it * 2 + } +} + +fun test3() { + val abc = ArrayList().map { + it * 2 + } +} + +fun testWithComments() { + val abc = ArrayList() + // .map { + // it * 2 + // } + .filter { + it > 4 + } +} + diff --git a/idea/testData/formatter/FunctionLiteralsInChainCalls.kt b/idea/testData/formatter/FunctionLiteralsInChainCalls.kt new file mode 100644 index 00000000000..20de104580b --- /dev/null +++ b/idea/testData/formatter/FunctionLiteralsInChainCalls.kt @@ -0,0 +1,45 @@ +import java.util.ArrayList + +fun test() { +val abc = ArrayList() + .map { + it * 2 + } + .filter { + it > 4 + } +} + +fun test1() { +val abc = ArrayList() + .map({ + it * 2 + }) + .filter({ + it > 4 + }) +} + +fun test2() { +val abc = ArrayList() + .map { + it * 2 + } +} + +fun test3() { +val abc = ArrayList().map { + it * 2 +} +} + +fun testWithComments() { + val abc = ArrayList() +// .map { +// it * 2 +// } + .filter { + it > 4 + } +} + diff --git a/idea/testData/formatter/SpacesInQualifiedExpressions.after.kt b/idea/testData/formatter/SpacesInQualifiedExpressions.after.kt new file mode 100644 index 00000000000..acab5f7bcb0 --- /dev/null +++ b/idea/testData/formatter/SpacesInQualifiedExpressions.after.kt @@ -0,0 +1,41 @@ +trait Test { + fun foo(): Test +} + +fun test(t: Test) { + t.foo() + + t. + foo() + + t. + + + foo() + + t + + + .foo() + + + t.foo().foo() + + + t?.foo() + + t?. + foo() + + t?. + + + foo() + + t + + + ?.foo() + + t?.foo()?.foo() +} \ No newline at end of file diff --git a/idea/testData/formatter/SpacesInQualifiedExpressions.kt b/idea/testData/formatter/SpacesInQualifiedExpressions.kt new file mode 100644 index 00000000000..c9783bc19da --- /dev/null +++ b/idea/testData/formatter/SpacesInQualifiedExpressions.kt @@ -0,0 +1,43 @@ +trait Test { + fun foo(): Test +} + +fun test(t: Test) { + t . foo() + + t. + foo() + + t. + + + foo() + + t + + + + .foo() + + + t.foo() . foo() + + + t ?. foo() + + t?. + foo() + + t?. + + + foo() + + t + + + + ?.foo() + + t?.foo() ?. foo() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/formatter/JetFormatterTestGenerated.java b/idea/tests/org/jetbrains/jet/formatter/JetFormatterTestGenerated.java index 59076345e98..093b014a789 100644 --- a/idea/tests/org/jetbrains/jet/formatter/JetFormatterTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/formatter/JetFormatterTestGenerated.java @@ -154,6 +154,11 @@ public class JetFormatterTestGenerated extends AbstractJetFormatterTest { doTest("idea/testData/formatter/FunctionLineBreak.after.kt"); } + @TestMetadata("FunctionLiteralsInChainCalls.after.kt") + public void testFunctionLiteralsInChainCalls() throws Exception { + doTest("idea/testData/formatter/FunctionLiteralsInChainCalls.after.kt"); + } + @TestMetadata("FunctionWithInference.after.kt") public void testFunctionWithInference() throws Exception { doTest("idea/testData/formatter/FunctionWithInference.after.kt"); @@ -294,6 +299,11 @@ public class JetFormatterTestGenerated extends AbstractJetFormatterTest { doTest("idea/testData/formatter/SpacesInDeclarations.after.kt"); } + @TestMetadata("SpacesInQualifiedExpressions.after.kt") + public void testSpacesInQualifiedExpressions() throws Exception { + doTest("idea/testData/formatter/SpacesInQualifiedExpressions.after.kt"); + } + @TestMetadata("TryCatchLineBreak.after.kt") public void testTryCatchLineBreak() throws Exception { doTest("idea/testData/formatter/TryCatchLineBreak.after.kt");