diff --git a/.idea/artifacts/KotlinPlugin.xml b/.idea/artifacts/KotlinPlugin.xml index 1eb3897786f..8c817a1fa8f 100644 --- a/.idea/artifacts/KotlinPlugin.xml +++ b/.idea/artifacts/KotlinPlugin.xml @@ -48,6 +48,7 @@ + diff --git a/idea/formatter/formatter.iml b/idea/formatter/formatter.iml index 8c779a87e85..efaee3d60da 100644 --- a/idea/formatter/formatter.iml +++ b/idea/formatter/formatter.iml @@ -5,11 +5,19 @@ - + - - - + + + + + + + + + + + \ No newline at end of file diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/CommonAlignmentStrategy.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/CommonAlignmentStrategy.kt new file mode 100644 index 00000000000..639150972fb --- /dev/null +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/CommonAlignmentStrategy.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2016 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.kotlin.idea.formatter + +import com.intellij.formatting.Alignment +import com.intellij.lang.ASTNode + +abstract class CommonAlignmentStrategy { + abstract fun getAlignment(node: ASTNode): Alignment? +} \ No newline at end of file diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt new file mode 100644 index 00000000000..94cba885d95 --- /dev/null +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt @@ -0,0 +1,435 @@ +/* + * Copyright 2010-2016 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.kotlin.idea.formatter + +import com.intellij.formatting.* +import com.intellij.lang.ASTNode +import com.intellij.psi.TokenType +import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.psi.tree.IElementType +import com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.kdoc.lexer.KDocTokens +import org.jetbrains.kotlin.psi.KtDeclaration +import java.util.* +import org.jetbrains.kotlin.idea.formatter.NodeIndentStrategy.strategy +import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings +import org.jetbrains.kotlin.kdoc.parser.KDocElementTypes +import org.jetbrains.kotlin.lexer.KtTokens.* + +private val QUALIFIED_OPERATION = TokenSet.create(DOT, SAFE_ACCESS) +private val KDOC_COMMENT_INDENT = 1 + +private val BINARY_EXPRESSIONS = TokenSet.create(KtNodeTypes.BINARY_EXPRESSION, KtNodeTypes.BINARY_WITH_TYPE, KtNodeTypes.IS_EXPRESSION) +private val KDOC_CONTENT = TokenSet.create(KDocTokens.KDOC, KDocElementTypes.KDOC_SECTION, KDocElementTypes.KDOC_TAG) + +private val CODE_BLOCKS = TokenSet.create(KtNodeTypes.BLOCK, KtNodeTypes.CLASS_BODY, KtNodeTypes.FUNCTION_LITERAL) + +private val ALIGN_FOR_BINARY_OPERATIONS = TokenSet.create(MUL, DIV, PERC, PLUS, MINUS, ELVIS, LT, GT, LTEQ, GTEQ, ANDAND, OROR) + +abstract class KotlinCommonBlock( + private val node: ASTNode, + private val settings: CodeStyleSettings, + private val spacingBuilder: KotlinSpacingBuilder, + private val alignmentStrategy: CommonAlignmentStrategy) { + private @Volatile var mySubBlocks: List? = null + + abstract protected fun createBlock(node: ASTNode, + alignmentStrategy: CommonAlignmentStrategy, + indent: Indent?, + wrap: Wrap?, + settings: CodeStyleSettings, + spacingBuilder: KotlinSpacingBuilder): Block + + abstract protected fun createSyntheticSpacingNodeBlock(node: ASTNode): ASTBlock + + abstract protected fun getSubBlocks(): List + + abstract protected fun getSuperChildAttributes(newChildIndex: Int): ChildAttributes + + abstract protected fun isIncompleteInSuper(): Boolean + + abstract protected fun getAlignmentForCaseBranch(shouldAlignInColumns: Boolean): CommonAlignmentStrategy + + abstract protected fun getAlignment(): Alignment? + + abstract protected fun createAlignmentStrategy(alignOption: Boolean, defaultAlignment: Alignment?): CommonAlignmentStrategy + + abstract protected fun getNullAlignmentStrategy(): CommonAlignmentStrategy + + fun isLeaf(): Boolean = node.firstChildNode == null + + fun buildChildren(): List { + if (mySubBlocks != null) { + return mySubBlocks!! + } + + var nodeSubBlocks = buildSubBlocks() + + if (node.elementType === KtNodeTypes.DOT_QUALIFIED_EXPRESSION || node.elementType === KtNodeTypes.SAFE_ACCESS_EXPRESSION) { + val 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). + + val operationBlock = nodeSubBlocks[operationBlockIndex] + val operationSyntheticBlock = SyntheticKotlinBlock( + (operationBlock as ASTBlock).node, + nodeSubBlocks.subList(operationBlockIndex, nodeSubBlocks.size), + null, operationBlock.getIndent(), null, spacingBuilder) { createSyntheticSpacingNodeBlock(it) } + + nodeSubBlocks = ArrayList(nodeSubBlocks.subList(0, operationBlockIndex)) + nodeSubBlocks.add(operationSyntheticBlock) + } + } + + mySubBlocks = nodeSubBlocks + + return nodeSubBlocks + } + + fun createChildIndent(child: ASTNode): Indent? { + val childParent = child.treeParent + val childType = child.elementType + + if (childParent != null && childParent.treeParent != null) { + if (childParent.elementType === KtNodeTypes.BLOCK && childParent.treeParent.elementType === KtNodeTypes.SCRIPT) { + return Indent.getNoneIndent() + } + } + + // do not indent child after heading comments inside declaration + if (childParent != null && childParent.psi is KtDeclaration) { + val prev = getPrevWithoutWhitespace(child) + if (prev != null && COMMENTS.contains(prev.elementType) && getPrevWithoutWhitespaceAndComments(prev) == null) { + return Indent.getNoneIndent() + } + } + + for (strategy in INDENT_RULES) { + val indent = strategy.getIndent(child) + if (indent != null) { + return indent + } + } + + // TODO: Try to rewrite other rules to declarative style + if (childParent != null) { + val parentType = childParent.elementType + + if (parentType === KtNodeTypes.VALUE_PARAMETER_LIST || parentType === KtNodeTypes.VALUE_ARGUMENT_LIST) { + val prev = getPrevWithoutWhitespace(child) + if (childType === RPAR && (prev == null || prev.elementType !== TokenType.ERROR_ELEMENT)) { + return Indent.getNoneIndent() + } + + return Indent.getContinuationWithoutFirstIndent() + } + + if (parentType === KtNodeTypes.TYPE_PARAMETER_LIST || parentType === KtNodeTypes.TYPE_ARGUMENT_LIST) { + return Indent.getContinuationWithoutFirstIndent() + } + } + + return Indent.getNoneIndent() + } + + fun getChildAttributes(newChildIndex: Int): ChildAttributes { + val type = node.elementType + return when (type) { + in CODE_BLOCKS, KtNodeTypes.WHEN, KtNodeTypes.IF, KtNodeTypes.FOR, KtNodeTypes.WHILE, KtNodeTypes.DO_WHILE -> ChildAttributes(Indent.getNormalIndent(), null) + + KtNodeTypes.TRY -> ChildAttributes(Indent.getNoneIndent(), null) + + KtNodeTypes.DOT_QUALIFIED_EXPRESSION, KtNodeTypes.SAFE_ACCESS_EXPRESSION -> ChildAttributes(Indent.getContinuationWithoutFirstIndent(), null) + + KtNodeTypes.VALUE_PARAMETER_LIST, KtNodeTypes.VALUE_ARGUMENT_LIST -> { + val subBlocks = getSubBlocks() + if (newChildIndex != 1 && newChildIndex != 0 && newChildIndex < subBlocks.size) { + val block = subBlocks[newChildIndex] + ChildAttributes(block.indent, block.alignment) + } + else { + ChildAttributes(Indent.getContinuationIndent(), null) + } + } + + DOC_COMMENT -> ChildAttributes(Indent.getSpaceIndent(KDOC_COMMENT_INDENT), null) + + KtNodeTypes.PARENTHESIZED -> getSuperChildAttributes(newChildIndex) + + else -> { + val blocks = getSubBlocks() + if (newChildIndex != 0) { + val isIncomplete = if (newChildIndex < blocks.size) blocks[newChildIndex - 1].isIncomplete else isIncompleteInSuper() + if (isIncomplete) { + return getSuperChildAttributes(newChildIndex) + } + } + + ChildAttributes(Indent.getNoneIndent(), null) + } + } + } + + private fun getChildrenAlignmentStrategy(): CommonAlignmentStrategy { + val jetCommonSettings = settings.getCommonSettings(KotlinLanguage.INSTANCE) + val jetSettings = settings.getCustomSettings(KotlinCodeStyleSettings::class.java) + val parentType = node.elementType + if (parentType === KtNodeTypes.VALUE_PARAMETER_LIST) { + return getAlignmentForChildInParenthesis( + jetCommonSettings.ALIGN_MULTILINE_PARAMETERS, KtNodeTypes.VALUE_PARAMETER, COMMA, + jetCommonSettings.ALIGN_MULTILINE_METHOD_BRACKETS, LPAR, RPAR) + } + else if (parentType === KtNodeTypes.VALUE_ARGUMENT_LIST) { + return getAlignmentForChildInParenthesis( + jetCommonSettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS, KtNodeTypes.VALUE_ARGUMENT, COMMA, + jetCommonSettings.ALIGN_MULTILINE_METHOD_BRACKETS, LPAR, RPAR) + } + else if (parentType === KtNodeTypes.WHEN) { + return getAlignmentForCaseBranch(jetSettings.ALIGN_IN_COLUMNS_CASE_BRANCH) + } + else if (parentType === KtNodeTypes.WHEN_ENTRY) { + return alignmentStrategy + } + else if (parentType in BINARY_EXPRESSIONS && getOperationType(node) in ALIGN_FOR_BINARY_OPERATIONS) { + return createAlignmentStrategy(jetCommonSettings.ALIGN_MULTILINE_BINARY_OPERATION, getAlignment()) + } + else if (parentType === KtNodeTypes.SUPER_TYPE_LIST || parentType === KtNodeTypes.INITIALIZER_LIST) { + return createAlignmentStrategy(jetCommonSettings.ALIGN_MULTILINE_EXTENDS_LIST, getAlignment()) + } + else if (parentType === KtNodeTypes.PARENTHESIZED) { + return object : CommonAlignmentStrategy() { + private var bracketsAlignment: Alignment? = if (jetCommonSettings.ALIGN_MULTILINE_BINARY_OPERATION) Alignment.createAlignment() else null + + override fun getAlignment(node: ASTNode): Alignment? { + val childNodeType = node.elementType + val prev = getPrevWithoutWhitespace(node) + + if (prev != null && prev.elementType === TokenType.ERROR_ELEMENT || childNodeType === TokenType.ERROR_ELEMENT) { + return bracketsAlignment + } + + if (childNodeType === LPAR || childNodeType === RPAR) { + return bracketsAlignment + } + + return null + } + } + } + + return getNullAlignmentStrategy() + } + + + private fun buildSubBlock(child: ASTNode, alignmentStrategy: CommonAlignmentStrategy, wrappingStrategy: WrappingStrategy): Block { + val childWrap = wrappingStrategy.getWrap(child.elementType) + + // Skip one sub-level for operators, so type of block node is an element type of operator + if (child.elementType === KtNodeTypes.OPERATION_REFERENCE) { + val operationNode = child.firstChildNode + if (operationNode != null) { + return createBlock( + operationNode, + alignmentStrategy, + createChildIndent(child), + childWrap, + settings, + spacingBuilder) + } + } + + return createBlock(child, alignmentStrategy, createChildIndent(child), childWrap, settings, spacingBuilder) + } + + private fun buildSubBlocks(): ArrayList { + val blocks = ArrayList() + + val childrenAlignmentStrategy = getChildrenAlignmentStrategy() + val wrappingStrategy = getWrappingStrategy() + + var child: ASTNode? = node.firstChildNode + while (child != null) { + val childType = child.elementType + + if (child.textRange.length == 0) { + child = child.treeNext + continue + } + + if (childType === TokenType.WHITE_SPACE) { + child = child.treeNext + continue + } + + blocks.add(buildSubBlock(child, childrenAlignmentStrategy, wrappingStrategy)) + child = child.treeNext + } + + return blocks + } + + private fun getWrappingStrategy(): WrappingStrategy { + val commonSettings = settings.getCommonSettings(KotlinLanguage.INSTANCE) + val elementType = node.elementType + + if (elementType === KtNodeTypes.VALUE_ARGUMENT_LIST) { + return getWrappingStrategyForItemList(commonSettings.CALL_PARAMETERS_WRAP, KtNodeTypes.VALUE_ARGUMENT) + } + if (elementType === KtNodeTypes.VALUE_PARAMETER_LIST) { + val parentElementType = node.treeParent.elementType + if (parentElementType === KtNodeTypes.FUN || parentElementType === KtNodeTypes.CLASS) { + return getWrappingStrategyForItemList(commonSettings.METHOD_PARAMETERS_WRAP, KtNodeTypes.VALUE_PARAMETER) + } + } + + return WrappingStrategy.NoWrapping + } +} + +private val INDENT_RULES = arrayOf( + strategy("No indent for braces in blocks") + .`in`(KtNodeTypes.BLOCK, KtNodeTypes.CLASS_BODY, KtNodeTypes.FUNCTION_LITERAL) + .forType(RBRACE, LBRACE) + .set(Indent.getNoneIndent()), + + strategy("Indent for block content") + .`in`(KtNodeTypes.BLOCK, KtNodeTypes.CLASS_BODY, KtNodeTypes.FUNCTION_LITERAL) + .notForType(RBRACE, LBRACE, KtNodeTypes.BLOCK) + .set(Indent.getNormalIndent(false)), + + strategy("Indent for property accessors") + .`in`(KtNodeTypes.PROPERTY).forType(KtNodeTypes.PROPERTY_ACCESSOR) + .set(Indent.getNormalIndent()), + + strategy("For a single statement in 'for'") + .`in`(KtNodeTypes.BODY).notForType(KtNodeTypes.BLOCK) + .set(Indent.getNormalIndent()), + + strategy("For the entry in when") + .forType(KtNodeTypes.WHEN_ENTRY) + .set(Indent.getNormalIndent()), + + strategy("For single statement in THEN and ELSE") + .`in`(KtNodeTypes.THEN, KtNodeTypes.ELSE).notForType(KtNodeTypes.BLOCK) + .set(Indent.getNormalIndent()), + + strategy("Indent for parts") + .`in`(KtNodeTypes.PROPERTY, KtNodeTypes.FUN, KtNodeTypes.DESTRUCTURING_DECLARATION) + .notForType(KtNodeTypes.BLOCK, FUN_KEYWORD, VAL_KEYWORD, VAR_KEYWORD) + .set(Indent.getContinuationWithoutFirstIndent()), + + strategy("Chained calls") + .`in`(KtNodeTypes.DOT_QUALIFIED_EXPRESSION, KtNodeTypes.SAFE_ACCESS_EXPRESSION) + .set(Indent.getContinuationWithoutFirstIndent(false)), + + strategy("Delegation list") + .`in`(KtNodeTypes.SUPER_TYPE_LIST, KtNodeTypes.INITIALIZER_LIST) + .set(Indent.getContinuationIndent(false)), + + strategy("Indices") + .`in`(KtNodeTypes.INDICES) + .set(Indent.getContinuationIndent(false)), + + strategy("Binary expressions") + .`in`(BINARY_EXPRESSIONS) + .set(Indent.getContinuationWithoutFirstIndent(false)), + + strategy("Parenthesized expression") + .`in`(KtNodeTypes.PARENTHESIZED) + .set(Indent.getContinuationWithoutFirstIndent(false)), + + strategy("KDoc comment indent") + .`in`(KDOC_CONTENT) + .forType(KDocTokens.LEADING_ASTERISK, KDocTokens.END) + .set(Indent.getSpaceIndent(KDOC_COMMENT_INDENT)), + + strategy("Block in when entry") + .`in`(KtNodeTypes.WHEN_ENTRY) + .notForType(KtNodeTypes.BLOCK, KtNodeTypes.WHEN_CONDITION_EXPRESSION, KtNodeTypes.WHEN_CONDITION_IN_RANGE, KtNodeTypes.WHEN_CONDITION_IS_PATTERN, ELSE_KEYWORD, ARROW) + .set(Indent.getNormalIndent())) + +private fun getOperationType(node: ASTNode): IElementType? = node.findChildByType(KtNodeTypes.OPERATION_REFERENCE)?.firstChildNode?.elementType + +private fun getAlignmentForChildInParenthesis( + shouldAlignChild: Boolean, parameter: IElementType, delimiter: IElementType, + shouldAlignParenthesis: Boolean, openBracket: IElementType, closeBracket: IElementType): CommonAlignmentStrategy { + val parameterAlignment = if (shouldAlignChild) Alignment.createAlignment() else null + val bracketsAlignment = if (shouldAlignParenthesis) Alignment.createAlignment() else null + + return object : CommonAlignmentStrategy() { + override fun getAlignment(node: ASTNode): Alignment? { + val childNodeType = node.elementType + + val prev = getPrevWithoutWhitespace(node) + if (prev != null && prev.elementType === TokenType.ERROR_ELEMENT || childNodeType === TokenType.ERROR_ELEMENT) { + // Prefer align to parameters on incomplete code (case of line break after comma, when next parameters is absent) + return parameterAlignment + } + + if (childNodeType === openBracket || childNodeType === closeBracket) { + return bracketsAlignment + } + + if (childNodeType === parameter || childNodeType === delimiter) { + return parameterAlignment + } + + return null + } + } +} + +private fun getPrevWithoutWhitespace(pNode: ASTNode?): ASTNode? { + var node = pNode + node = node!!.treePrev + while (node != null && node.elementType === TokenType.WHITE_SPACE) { + node = node.treePrev + } + + return node +} + +private fun getPrevWithoutWhitespaceAndComments(pNode: ASTNode?): ASTNode? { + var node = pNode + node = node!!.treePrev + while (node != null && (node.elementType === TokenType.WHITE_SPACE || COMMENTS.contains(node.elementType))) { + node = node.treePrev + } + + return node +} + +private fun getWrappingStrategyForItemList(wrapType: Int, itemType: IElementType): WrappingStrategy { + val itemWrap = Wrap.createWrap(wrapType, false) + return object : WrappingStrategy { + override fun getWrap(childElementType: IElementType): Wrap? { + return if (childElementType === itemType) itemWrap else null + } + } +} + +private fun findNodeBlockIndex(blocks: List, tokenSet: TokenSet): Int { + return blocks.indexOfFirst { block -> + if (block !is ASTBlock) return@indexOfFirst false + + val node = block.node + node != null && node.elementType in tokenSet + } +} \ No newline at end of file diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinSpacingBuilder.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinSpacingBuilder.kt index 9f99052894b..fff92839c7b 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinSpacingBuilder.kt +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinSpacingBuilder.kt @@ -22,7 +22,6 @@ import com.intellij.formatting.DependentSpacingRule.Trigger import com.intellij.lang.ASTNode import com.intellij.openapi.util.TextRange import com.intellij.psi.codeStyle.CodeStyleSettings -import com.intellij.psi.formatter.common.AbstractBlock import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.KtNodeTypes @@ -30,13 +29,7 @@ import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.lexer.KtTokens import java.util.ArrayList -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 - } - +class KotlinSpacingBuilder(val codeStyleSettings: CodeStyleSettings, val spacingBuilderUtil: KotlinSpacingBuilderUtil) { private val builders = ArrayList() private interface Builder { @@ -89,12 +82,13 @@ class KotlinSpacingBuilder(val codeStyleSettings: CodeStyleSettings) { fun emptyLinesIfLineBreakInLeft(emptyLines: Int, numberOfLineFeedsOtherwise: Int = 1, numSpacesOtherwise: Int = 0) { newRule { parent: ASTBlock, left: ASTBlock, right: ASTBlock -> val dependentSpacingRule = DependentSpacingRule(Trigger.HAS_LINE_FEEDS).registerData(Anchor.MIN_LINE_FEEDS, emptyLines + 1) - LineFeedDependantSpacing( - numSpacesOtherwise, numSpacesOtherwise, - minimumLineFeeds = numberOfLineFeedsOtherwise, - keepLineBreaks = codeStyleSettings.KEEP_LINE_BREAKS, - keepBlankLines = codeStyleSettings.KEEP_BLANK_LINES_IN_DECLARATIONS, - dependency = left.textRange, rule = dependentSpacingRule) + spacingBuilderUtil.createLineFeedDependentSpacing(numSpacesOtherwise, + numSpacesOtherwise, + numberOfLineFeedsOtherwise, + codeStyleSettings.KEEP_LINE_BREAKS, + codeStyleSettings.KEEP_BLANK_LINES_IN_DECLARATIONS, + left.textRange, + dependentSpacingRule) } } @@ -145,24 +139,24 @@ class KotlinSpacingBuilder(val codeStyleSettings: CodeStyleSettings) { builder.init() builders.add(builder) } - - private class LineFeedDependantSpacing( - minSpaces: Int, - maxSpaces: Int, - val minimumLineFeeds: Int, - keepLineBreaks: Boolean, - keepBlankLines: Int, - dependency: TextRange, - rule: DependentSpacingRule) : DependantSpacingImpl(minSpaces, maxSpaces, dependency, keepLineBreaks, keepBlankLines, rule) { - override fun getMinLineFeeds(): Int { - val superMin = super.getMinLineFeeds() - return if (superMin == 0) minimumLineFeeds else superMin - } - } } -fun rules(codeStyleSettings: CodeStyleSettings, init: KotlinSpacingBuilder.() -> Unit): KotlinSpacingBuilder { - val builder = KotlinSpacingBuilder(codeStyleSettings) +interface KotlinSpacingBuilderUtil { + fun createLineFeedDependentSpacing(minSpaces: Int, + maxSpaces: Int, + minimumLineFeeds: Int, + keepLineBreaks: Boolean, + keepBlankLines: Int, + dependency: TextRange, + rule: DependentSpacingRule): Spacing + + fun getPreviousNonWhitespaceLeaf(node: ASTNode?): ASTNode? + + fun isWhitespaceOrEmpty(node: ASTNode?): Boolean +} + +fun rules(codeStyleSettings: CodeStyleSettings, builderUtil: KotlinSpacingBuilderUtil, init: KotlinSpacingBuilder.() -> Unit): KotlinSpacingBuilder { + val builder = KotlinSpacingBuilder(codeStyleSettings, builderUtil) builder.init() return builder } diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/SyntheticKotlinBlock.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/SyntheticKotlinBlock.kt index 2e3161a069a..960b7fbd0d9 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/SyntheticKotlinBlock.kt +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/SyntheticKotlinBlock.kt @@ -24,7 +24,6 @@ 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 class SyntheticKotlinBlock( @@ -33,7 +32,8 @@ class SyntheticKotlinBlock( private val alignment: Alignment?, private val indent: Indent?, private val wrap: Wrap?, - private val spacingBuilder: KotlinSpacingBuilder + private val spacingBuilder: KotlinSpacingBuilder, + private val createSyntheticSpacingNodeBlock: (ASTNode) -> ASTBlock ) : ASTBlock { private val textRange = TextRange( @@ -49,8 +49,9 @@ class SyntheticKotlinBlock( 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.treeParent!!), child1, child2) + override fun getSpacing(child1: Block?, child2: Block): Spacing? { + return spacingBuilder.getSpacing(createSyntheticSpacingNodeBlock(node.treeParent!!), child1, child2) + } override fun toString(): String { @@ -59,12 +60,10 @@ class SyntheticKotlinBlock( loop@ while (treeNode == null) when (child) { - is AbstractBlock -> { - treeNode = child.node - } - is SyntheticKotlinBlock -> { - child = child.getSubBlocks().first() - } + is SyntheticKotlinBlock -> child = child.getSubBlocks().first() + + is ASTBlock -> treeNode = child.node + else -> break@loop } @@ -81,4 +80,4 @@ class SyntheticKotlinBlock( return javaClass.name + ": " + textRange } -} +} \ No newline at end of file diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/kotlinSpacingRules.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/kotlinSpacingRules.kt index fb36b1f803d..b761988c64d 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/kotlinSpacingRules.kt +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/kotlinSpacingRules.kt @@ -22,8 +22,9 @@ import com.intellij.formatting.SpacingBuilder import com.intellij.formatting.SpacingBuilder.RuleBuilder import com.intellij.lang.ASTNode import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.TokenType import com.intellij.psi.codeStyle.CodeStyleSettings -import com.intellij.psi.formatter.FormatterUtil +import com.intellij.psi.impl.source.tree.TreeUtil import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.KtNodeTypes.* @@ -44,11 +45,11 @@ fun SpacingBuilder.afterInside(element: IElementType, tokenSet: TokenSet, spacin tokenSet.types.forEach { inType -> afterInside(element, inType).spacingFun() } } -fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder { +fun createSpacingBuilder(settings: CodeStyleSettings, builderUtil: KotlinSpacingBuilderUtil): KotlinSpacingBuilder { val kotlinSettings = settings.getCustomSettings(KotlinCodeStyleSettings::class.java)!! val kotlinCommonSettings = settings.getCommonSettings(KotlinLanguage.INSTANCE)!! - return rules(settings) { + return rules(settings, builderUtil) { val DECLARATIONS = TokenSet.create(PROPERTY, FUN, CLASS, OBJECT_DECLARATION, ENUM_ENTRY, SECONDARY_CONSTRUCTOR, CLASS_INITIALIZER) @@ -65,7 +66,7 @@ fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder { inPosition(left = FUN, right = CLASS).emptyLinesIfLineBreakInLeft(1) inPosition(left = ENUM_ENTRY, right = ENUM_ENTRY).emptyLinesIfLineBreakInLeft( - emptyLines = 0, numSpacesOtherwise = 1, numberOfLineFeedsOtherwise = 0) + emptyLines = 0, numberOfLineFeedsOtherwise = 0, numSpacesOtherwise = 1) inPosition(parent = CLASS_BODY, left = SEMICOLON).customRule { parent, left, right -> val klass = parent.node.treeParent.psi as? KtClass ?: return@customRule null @@ -249,7 +250,7 @@ fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder { inPosition(parent = parent, right = keyword).customRule { parent, left, right -> - val previousLeaf = FormatterUtil.getPreviousNonWhitespaceLeaf(right.node) + val previousLeaf = builderUtil.getPreviousNonWhitespaceLeaf(right.node) val leftBlock = if ( previousLeaf != null && previousLeaf.elementType == RBRACE && @@ -277,7 +278,7 @@ fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder { if (block != null && block.elementType == blockType) { val leftBrace = block.findChildByType(LBRACE) if (leftBrace != null) { - val previousLeaf = FormatterUtil.getPreviousNonWhitespaceLeaf(leftBrace) + val previousLeaf = builderUtil.getPreviousNonWhitespaceLeaf(leftBrace) val isAfterEolComment = previousLeaf != null && (previousLeaf.elementType == EOL_COMMENT) val keepLineBreaks = kotlinSettings.LBRACE_ON_NEXT_LINE || isAfterEolComment val minimumLF = if (kotlinSettings.LBRACE_ON_NEXT_LINE) 1 else 0 @@ -390,4 +391,4 @@ fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder { after(EOL_COMMENT).lineBreakInCode() } } -} +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinBlock.kt b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinBlock.kt index bc24e090d4e..063119979a5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinBlock.kt +++ b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinBlock.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 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. @@ -19,428 +19,110 @@ package org.jetbrains.kotlin.idea.formatter import com.intellij.formatting.* import com.intellij.formatting.alignment.AlignmentStrategy import com.intellij.lang.ASTNode -import com.intellij.psi.TokenType +import com.intellij.openapi.util.TextRange import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.psi.formatter.FormatterUtil 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.kotlin.KtNodeTypes.* -import org.jetbrains.kotlin.idea.KotlinLanguage -import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings -import org.jetbrains.kotlin.idea.formatter.NodeIndentStrategy.strategy -import org.jetbrains.kotlin.kdoc.lexer.KDocTokens -import org.jetbrains.kotlin.kdoc.parser.KDocElementTypes -import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens.* -import org.jetbrains.kotlin.psi.KtDeclaration -import java.util.* - -private val KDOC_COMMENT_INDENT = 1 - -private val BINARY_EXPRESSIONS = TokenSet.create(BINARY_EXPRESSION, BINARY_WITH_TYPE, IS_EXPRESSION) -private val QUALIFIED_OPERATION = TokenSet.create(DOT, SAFE_ACCESS) -private val ALIGN_FOR_BINARY_OPERATIONS = TokenSet.create(MUL, DIV, PERC, PLUS, MINUS, ELVIS, LT, GT, LTEQ, GTEQ, ANDAND, OROR) - -private val CODE_BLOCKS = TokenSet.create(BLOCK, CLASS_BODY, FUNCTION_LITERAL) - -private val KDOC_CONTENT = TokenSet.create(KDocTokens.KDOC, KDocElementTypes.KDOC_SECTION, KDocElementTypes.KDOC_TAG) /** * @see Block for good JavaDoc documentation */ class KotlinBlock( node: ASTNode, - private val myAlignmentStrategy: NodeAlignmentStrategy, + private val myAlignmentStrategy: CommonAlignmentStrategy, private val myIndent: Indent?, wrap: Wrap?, - private val mySettings: CodeStyleSettings, + mySettings: CodeStyleSettings, private val mySpacingBuilder: KotlinSpacingBuilder) : AbstractBlock(node, wrap, myAlignmentStrategy.getAlignment(node)) { - private var mySubBlocks: List? = null + private val kotlinDelegationBlock = object : KotlinCommonBlock(node, mySettings, mySpacingBuilder, myAlignmentStrategy) { + override fun getNullAlignmentStrategy(): CommonAlignmentStrategy = NodeAlignmentStrategy.getNullStrategy() - override fun getIndent(): Indent? { - return myIndent - } - - override fun buildChildren(): List { - if (mySubBlocks == null) { - var nodeSubBlocks = buildSubBlocks() as ArrayList - - if (node.elementType === DOT_QUALIFIED_EXPRESSION || node.elementType === SAFE_ACCESS_EXPRESSION) { - val 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). - - val operationBlock = nodeSubBlocks[operationBlockIndex] - val operationSyntheticBlock = SyntheticKotlinBlock( - (operationBlock as ASTBlock).node, - nodeSubBlocks.subList(operationBlockIndex, nodeSubBlocks.size), - null, operationBlock.getIndent(), null, mySpacingBuilder) - - nodeSubBlocks = ContainerUtil.addAll( - ContainerUtil.newArrayList(nodeSubBlocks.subList(0, operationBlockIndex)), - operationSyntheticBlock) - } - } - - mySubBlocks = nodeSubBlocks - } - return mySubBlocks!! - } - - private fun buildSubBlocks(): List { - val blocks = ArrayList() - - val childrenAlignmentStrategy = getChildrenAlignmentStrategy() - val wrappingStrategy = getWrappingStrategy() - - var child: ASTNode? = myNode.firstChildNode - while (child != null) { - val childType = child.elementType - - if (child.textRange.length == 0) { - child = child.treeNext - continue - } - - if (childType === TokenType.WHITE_SPACE) { - child = child.treeNext - continue - } - - blocks.add(buildSubBlock(child, childrenAlignmentStrategy, wrappingStrategy)) - child = child.treeNext + override fun createAlignmentStrategy(alignOption: Boolean, defaultAlignment: Alignment?): CommonAlignmentStrategy { + return NodeAlignmentStrategy.fromTypes(AlignmentStrategy.wrap(createAlignment(alignOption, defaultAlignment))) } - return blocks - } - - private fun buildSubBlock( - child: ASTNode, - alignmentStrategy: NodeAlignmentStrategy, - wrappingStrategy: WrappingStrategy): Block { - val wrap = wrappingStrategy.getWrap(child.elementType) - - // Skip one sub-level for operators, so type of block node is an element type of operator - if (child.elementType === OPERATION_REFERENCE) { - val operationNode = child.firstChildNode - if (operationNode != null) { - return KotlinBlock(operationNode, alignmentStrategy, createChildIndent(child), wrap, mySettings, mySpacingBuilder) + override fun getAlignmentForCaseBranch(shouldAlignInColumns: Boolean): CommonAlignmentStrategy { + return if (shouldAlignInColumns) { + NodeAlignmentStrategy.fromTypes( + AlignmentStrategy.createAlignmentPerTypeStrategy(listOf(ARROW as IElementType), WHEN_ENTRY, true)) + } + else { + NodeAlignmentStrategy.getNullStrategy() } } - return KotlinBlock(child, alignmentStrategy, createChildIndent(child), wrap, mySettings, mySpacingBuilder) + override fun getAlignment(): Alignment? = alignment + + override fun isIncompleteInSuper(): Boolean = this@KotlinBlock.isIncomplete + + override fun getSuperChildAttributes(newChildIndex: Int): ChildAttributes = super@KotlinBlock.getChildAttributes(newChildIndex) + + override fun getSubBlocks(): List = subBlocks + + override fun createBlock(node: ASTNode, alignmentStrategy: CommonAlignmentStrategy, indent: Indent?, wrap: Wrap?, settings: CodeStyleSettings, spacingBuilder: KotlinSpacingBuilder): Block { + return KotlinBlock( + node, + alignmentStrategy, + indent, + wrap, + mySettings, + mySpacingBuilder) + } + + override fun createSyntheticSpacingNodeBlock(node: ASTNode): ASTBlock { + return object : AbstractBlock(node, null, null) { + override fun isLeaf(): Boolean = false + override fun getSpacing(child1: Block?, child2: Block): Spacing? = null + override fun buildChildren(): List = emptyList() + } + } } + override fun getIndent(): Indent? = myIndent + + override fun buildChildren(): List = kotlinDelegationBlock.buildChildren() + override fun getSpacing(child1: Block?, child2: Block): Spacing? = mySpacingBuilder.getSpacing(this, child1, child2) - override fun getChildAttributes(newChildIndex: Int): ChildAttributes { - val type = node.elementType - return when (type) { - in CODE_BLOCKS, WHEN, IF, FOR, WHILE, DO_WHILE -> ChildAttributes(Indent.getNormalIndent(), null) + override fun getChildAttributes(newChildIndex: Int): ChildAttributes = kotlinDelegationBlock.getChildAttributes(newChildIndex) - TRY -> ChildAttributes(Indent.getNoneIndent(), null) - - DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION -> ChildAttributes(Indent.getContinuationWithoutFirstIndent(), null) - - VALUE_PARAMETER_LIST, VALUE_ARGUMENT_LIST -> { - if (newChildIndex != 1 && newChildIndex != 0 && newChildIndex < subBlocks.size) { - val block = subBlocks[newChildIndex] - ChildAttributes(block.indent, block.alignment) - } - else { - ChildAttributes(Indent.getContinuationIndent(), null) - } - } - - DOC_COMMENT -> ChildAttributes(Indent.getSpaceIndent(KDOC_COMMENT_INDENT), null) - - PARENTHESIZED -> super.getChildAttributes(newChildIndex) - - else -> { - val blocks = subBlocks - if (newChildIndex != 0) { - val isIncomplete = if (newChildIndex < blocks.size) blocks[newChildIndex - 1].isIncomplete else isIncomplete - if (isIncomplete) { - return super.getChildAttributes(newChildIndex) - } - } - - ChildAttributes(Indent.getNoneIndent(), null) - } - } - } - - override fun isLeaf(): Boolean = myNode.firstChildNode == null - - private fun getWrappingStrategy(): WrappingStrategy { - val commonSettings = mySettings.getCommonSettings(KotlinLanguage.INSTANCE) - val elementType = myNode.elementType - - if (elementType === VALUE_ARGUMENT_LIST) { - return getWrappingStrategyForItemList(commonSettings.CALL_PARAMETERS_WRAP, VALUE_ARGUMENT) - } - if (elementType === VALUE_PARAMETER_LIST) { - val parentElementType = myNode.treeParent.elementType - if (parentElementType === FUN || parentElementType === CLASS) { - return getWrappingStrategyForItemList(commonSettings.METHOD_PARAMETERS_WRAP, VALUE_PARAMETER) - } - } - - return WrappingStrategy.NoWrapping - } - - // Redefine list of strategies for some special elements - // Propagate when alignment for -> - private fun getChildrenAlignmentStrategy(): NodeAlignmentStrategy { - val jetCommonSettings = mySettings.getCommonSettings(KotlinLanguage.INSTANCE) - val jetSettings = mySettings.getCustomSettings(KotlinCodeStyleSettings::class.java) - val parentType = myNode.elementType - if (parentType === VALUE_PARAMETER_LIST) { - return getAlignmentForChildInParenthesis( - jetCommonSettings.ALIGN_MULTILINE_PARAMETERS, VALUE_PARAMETER, COMMA, - jetCommonSettings.ALIGN_MULTILINE_METHOD_BRACKETS, LPAR, RPAR) - } - else if (parentType === VALUE_ARGUMENT_LIST) { - return getAlignmentForChildInParenthesis( - jetCommonSettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS, VALUE_ARGUMENT, COMMA, - jetCommonSettings.ALIGN_MULTILINE_METHOD_BRACKETS, LPAR, RPAR) - } - else if (parentType === WHEN) { - return getAlignmentForCaseBranch(jetSettings.ALIGN_IN_COLUMNS_CASE_BRANCH) - } - else if (parentType === WHEN_ENTRY) { - return myAlignmentStrategy - } - else if (parentType in BINARY_EXPRESSIONS && getOperationType(node) in ALIGN_FOR_BINARY_OPERATIONS) { - return NodeAlignmentStrategy.fromTypes(AlignmentStrategy.wrap( - createAlignment(jetCommonSettings.ALIGN_MULTILINE_BINARY_OPERATION, alignment))) - } - else if (parentType === SUPER_TYPE_LIST || parentType === INITIALIZER_LIST) { - return NodeAlignmentStrategy.fromTypes(AlignmentStrategy.wrap( - createAlignment(jetCommonSettings.ALIGN_MULTILINE_EXTENDS_LIST, alignment))) - } - else if (parentType === PARENTHESIZED) { - return object : NodeAlignmentStrategy() { - private var bracketsAlignment: Alignment? = if (jetCommonSettings.ALIGN_MULTILINE_BINARY_OPERATION) Alignment.createAlignment() else null - - override fun getAlignment(childNode: ASTNode): Alignment? { - val childNodeType = childNode.elementType - val prev = getPrevWithoutWhitespace(childNode) - - if (prev != null && prev.elementType === TokenType.ERROR_ELEMENT || childNodeType === TokenType.ERROR_ELEMENT) { - return bracketsAlignment - } - - if (childNodeType === LPAR || childNodeType === RPAR) { - return bracketsAlignment - } - - return null - } - } - } - - return NodeAlignmentStrategy.getNullStrategy() - } + override fun isLeaf(): Boolean = kotlinDelegationBlock.isLeaf() } -private val INDENT_RULES = arrayOf( - strategy("No indent for braces in blocks") - .`in`(BLOCK, CLASS_BODY, FUNCTION_LITERAL) - .forType(RBRACE, LBRACE) - .set(Indent.getNoneIndent()), - - strategy("Indent for block content") - .`in`(BLOCK, CLASS_BODY, FUNCTION_LITERAL) - .notForType(RBRACE, LBRACE, BLOCK) - .set(Indent.getNormalIndent(false)), - - strategy("Indent for property accessors") - .`in`(PROPERTY).forType(PROPERTY_ACCESSOR) - .set(Indent.getNormalIndent()), - - strategy("For a single statement in 'for'") - .`in`(BODY).notForType(BLOCK) - .set(Indent.getNormalIndent()), - - strategy("For the entry in when") - .forType(WHEN_ENTRY) - .set(Indent.getNormalIndent()), - - strategy("For single statement in THEN and ELSE") - .`in`(THEN, ELSE).notForType(BLOCK) - .set(Indent.getNormalIndent()), - - strategy("Indent for parts") - .`in`(PROPERTY, FUN, DESTRUCTURING_DECLARATION) - .notForType(BLOCK, FUN_KEYWORD, VAL_KEYWORD, VAR_KEYWORD) - .set(Indent.getContinuationWithoutFirstIndent()), - - strategy("Chained calls") - .`in`(DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION) - .set(Indent.getContinuationWithoutFirstIndent(false)), - - strategy("Delegation list") - .`in`(SUPER_TYPE_LIST, INITIALIZER_LIST) - .set(Indent.getContinuationIndent(false)), - - strategy("Indices") - .`in`(INDICES) - .set(Indent.getContinuationIndent(false)), - - strategy("Binary expressions") - .`in`(BINARY_EXPRESSIONS) - .set(Indent.getContinuationWithoutFirstIndent(false)), - - strategy("Parenthesized expression") - .`in`(PARENTHESIZED) - .set(Indent.getContinuationWithoutFirstIndent(false)), - - strategy("KDoc comment indent") - .`in`(KDOC_CONTENT) - .forType(KDocTokens.LEADING_ASTERISK, KDocTokens.END) - .set(Indent.getSpaceIndent(KDOC_COMMENT_INDENT)), - - strategy("Block in when entry") - .`in`(WHEN_ENTRY) - .notForType(BLOCK, WHEN_CONDITION_EXPRESSION, WHEN_CONDITION_IN_RANGE, WHEN_CONDITION_IS_PATTERN, ELSE_KEYWORD, ARROW) - .set(Indent.getNormalIndent())) - -private fun getPrevWithoutWhitespace(pNode: ASTNode?): ASTNode? { - var node = pNode - node = node!!.treePrev - while (node != null && node.elementType === TokenType.WHITE_SPACE) { - node = node.treePrev +object KotlinSpacingBuilderUtilImpl : KotlinSpacingBuilderUtil { + override fun getPreviousNonWhitespaceLeaf(node: ASTNode?): ASTNode? { + return FormatterUtil.getPreviousNonWhitespaceLeaf(node) } - return node -} - -private fun getPrevWithoutWhitespaceAndComments(pNode: ASTNode?): ASTNode? { - var node = pNode - node = node!!.treePrev - while (node != null && (node.elementType === TokenType.WHITE_SPACE || KtTokens.COMMENTS.contains(node.elementType))) { - node = node.treePrev + override fun isWhitespaceOrEmpty(node: ASTNode?): Boolean { + return FormatterUtil.isWhitespaceOrEmpty(node) } - return node -} - -private fun getWrappingStrategyForItemList(wrapType: Int, itemType: IElementType): WrappingStrategy { - val itemWrap = Wrap.createWrap(wrapType, false) - return object : WrappingStrategy { - override fun getWrap(childElementType: IElementType): Wrap? { - return if (childElementType === itemType) itemWrap else null - } - } -} - -private fun getAlignmentForChildInParenthesis( - shouldAlignChild: Boolean, parameter: IElementType, delimiter: IElementType, - shouldAlignParenthesis: Boolean, openBracket: IElementType, closeBracket: IElementType): NodeAlignmentStrategy { - val parameterAlignment = if (shouldAlignChild) Alignment.createAlignment() else null - val bracketsAlignment = if (shouldAlignParenthesis) Alignment.createAlignment() else null - - return object : NodeAlignmentStrategy() { - override fun getAlignment(node: ASTNode): Alignment? { - val childNodeType = node.elementType - - val prev = getPrevWithoutWhitespace(node) - if (prev != null && prev.elementType === TokenType.ERROR_ELEMENT || childNodeType === TokenType.ERROR_ELEMENT) { - // Prefer align to parameters on incomplete code (case of line break after comma, when next parameters is absent) - return parameterAlignment + override fun createLineFeedDependentSpacing( + minSpaces: Int, + maxSpaces: Int, + minimumLineFeeds: Int, + keepLineBreaks: Boolean, + keepBlankLines: Int, + dependency: TextRange, + rule: DependentSpacingRule): Spacing { + return object : DependantSpacingImpl(minSpaces, maxSpaces, dependency, keepLineBreaks, keepBlankLines, rule) { + override fun getMinLineFeeds(): Int { + val superMin = super.getMinLineFeeds() + return if (superMin == 0) minimumLineFeeds else superMin } - - if (childNodeType === openBracket || childNodeType === closeBracket) { - return bracketsAlignment - } - - if (childNodeType === parameter || childNodeType === delimiter) { - return parameterAlignment - } - - return null } } } -private fun getAlignmentForCaseBranch(shouldAlignInColumns: Boolean): NodeAlignmentStrategy { - return if (shouldAlignInColumns) { - NodeAlignmentStrategy.fromTypes( - AlignmentStrategy.createAlignmentPerTypeStrategy(listOf(ARROW as IElementType), WHEN_ENTRY, true)) - } - else { - NodeAlignmentStrategy.getNullStrategy() - } -} - -private fun createChildIndent(child: ASTNode): Indent? { - val childParent = child.treeParent - val childType = child.elementType - - if (childParent != null && childParent.treeParent != null) { - if (childParent.elementType === BLOCK && childParent.treeParent.elementType === SCRIPT) { - return Indent.getNoneIndent() - } - } - - // do not indent child after heading comments inside declaration - if (childParent != null && childParent.psi is KtDeclaration) { - val prev = getPrevWithoutWhitespace(child) - if (prev != null && prev.elementType in KtTokens.COMMENTS && getPrevWithoutWhitespaceAndComments(prev) == null) { - return Indent.getNoneIndent() - } - } - - for (strategy in INDENT_RULES) { - val indent = strategy.getIndent(child) - if (indent != null) { - return indent - } - } - - // TODO: Try to rewrite other rules to declarative style - if (childParent != null) { - val parentType = childParent.elementType - - if (parentType === VALUE_PARAMETER_LIST || parentType === VALUE_ARGUMENT_LIST) { - val prev = getPrevWithoutWhitespace(child) - if (childType === RPAR && (prev == null || prev.elementType !== TokenType.ERROR_ELEMENT)) { - return Indent.getNoneIndent() - } - - return Indent.getContinuationWithoutFirstIndent() - } - - if (parentType === TYPE_PARAMETER_LIST || parentType === TYPE_ARGUMENT_LIST) { - return Indent.getContinuationWithoutFirstIndent() - } - } - - return Indent.getNoneIndent() -} - private fun createAlignment(alignOption: Boolean, defaultAlignment: Alignment?): Alignment? { return if (alignOption) createAlignmentOrDefault(null, defaultAlignment) else defaultAlignment } private fun createAlignmentOrDefault(base: Alignment?, defaultAlignment: Alignment?): Alignment? { return defaultAlignment ?: if (base == null) Alignment.createAlignment() else Alignment.createChildAlignment(base) -} - -private fun findNodeBlockIndex(blocks: List, tokenSet: TokenSet): Int { - return blocks.indexOfFirst { block -> - if (block !is ASTBlock) return@indexOfFirst false - - val node = block.node - node != null && node.elementType in tokenSet - } -} - -private fun getOperationType(node: ASTNode): IElementType? { - val operationNode = node.findChildByType(OPERATION_REFERENCE) - return operationNode?.firstChildNode?.elementType; } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinFormattingModelBuilder.java b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinFormattingModelBuilder.java index 2444504ea11..67dc25bea63 100644 --- a/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinFormattingModelBuilder.java +++ b/idea/src/org/jetbrains/kotlin/idea/formatter/KotlinFormattingModelBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 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. @@ -38,7 +38,7 @@ public class KotlinFormattingModelBuilder implements FormattingModelBuilder { PsiFile containingFile = element.getContainingFile().getViewProvider().getPsi(KotlinLanguage.INSTANCE); KotlinBlock block = new KotlinBlock( containingFile.getNode(), NodeAlignmentStrategy.getNullStrategy(), Indent.getNoneIndent(), null, settings, - KotlinSpacingRulesKt.createSpacingBuilder(settings)); + KotlinSpacingRulesKt.createSpacingBuilder(settings, KotlinSpacingBuilderUtilImpl.INSTANCE)); //TODO: this is temporary code to allow formatting non-physical files in non-UI thread (used by conversion from Java to Kotlin) // it's needed until IDEA's issue with this document being created with wrong threading policy is fixed diff --git a/idea/src/org/jetbrains/kotlin/idea/formatter/NodeAlignmentStrategy.java b/idea/src/org/jetbrains/kotlin/idea/formatter/NodeAlignmentStrategy.java index 1135929520c..20c96bdd86f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/formatter/NodeAlignmentStrategy.java +++ b/idea/src/org/jetbrains/kotlin/idea/formatter/NodeAlignmentStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 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. @@ -22,7 +22,7 @@ import com.intellij.lang.ASTNode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public abstract class NodeAlignmentStrategy { +public abstract class NodeAlignmentStrategy extends CommonAlignmentStrategy { private static final NodeAlignmentStrategy NULL_STRATEGY = fromTypes(AlignmentStrategy.wrap(null)); @@ -35,10 +35,11 @@ public abstract class NodeAlignmentStrategy { return new AlignmentStrategyWrapper(strategy); } + @Override @Nullable public abstract Alignment getAlignment(@NotNull ASTNode node); - public static class AlignmentStrategyWrapper extends NodeAlignmentStrategy { + private static class AlignmentStrategyWrapper extends NodeAlignmentStrategy { private final AlignmentStrategy internalStrategy; public AlignmentStrategyWrapper(@NotNull AlignmentStrategy internalStrategy) { diff --git a/ultimate/.idea/libraries/main_project_production.xml b/ultimate/.idea/libraries/main_project_production.xml index 4effdebc0dc..5a4296b76f2 100644 --- a/ultimate/.idea/libraries/main_project_production.xml +++ b/ultimate/.idea/libraries/main_project_production.xml @@ -63,6 +63,7 @@ + @@ -94,6 +95,7 @@ + \ No newline at end of file