Split KotlinBlock on two clases to share independent logic about

formatting  between Idea and Eclipse

 Also make 'formatter' module depend on fewer dependencies
This commit is contained in:
Mikhail Zarechenskiy
2016-03-20 23:24:48 +03:00
parent 4c006300ef
commit 37e0b71584
11 changed files with 586 additions and 439 deletions
+1
View File
@@ -48,6 +48,7 @@
<element id="module-output" name="idea-live-templates" /> <element id="module-output" name="idea-live-templates" />
<element id="module-output" name="resolution" /> <element id="module-output" name="resolution" />
<element id="module-output" name="idea-ultimate" /> <element id="module-output" name="idea-ultimate" />
<element id="module-output" name="formatter" />
</element> </element>
<element id="library" level="project" name="javax.inject" /> <element id="library" level="project" name="javax.inject" />
<element id="directory" name="jps"> <element id="directory" name="jps">
+12 -4
View File
@@ -5,11 +5,19 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-runtime" level="project" /> <orderEntry type="library" name="kotlin-runtime" level="project" />
<orderEntry type="library" name="idea-full" level="project" /> <orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="idea-core" /> <orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="frontend.java" /> <orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../ideaSDK/lib/openapi.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component> </component>
</module> </module>
@@ -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?
}
@@ -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<Block>? = 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<Block>
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<Block> {
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<Block>(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<Block> {
val blocks = ArrayList<Block>()
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<NodeIndentStrategy>(
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<Block>, tokenSet: TokenSet): Int {
return blocks.indexOfFirst { block ->
if (block !is ASTBlock) return@indexOfFirst false
val node = block.node
node != null && node.elementType in tokenSet
}
}
@@ -22,7 +22,6 @@ import com.intellij.formatting.DependentSpacingRule.Trigger
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.codeStyle.CodeStyleSettings import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.common.AbstractBlock
import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.TokenSet import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.KtNodeTypes
@@ -30,13 +29,7 @@ import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import java.util.ArrayList import java.util.ArrayList
class KotlinSpacingBuilder(val codeStyleSettings: CodeStyleSettings) { class KotlinSpacingBuilder(val codeStyleSettings: CodeStyleSettings, val spacingBuilderUtil: KotlinSpacingBuilderUtil) {
class SpacingNodeBlock(node: ASTNode): AbstractBlock(node, null, null) {
override fun buildChildren(): MutableList<Block>? = ArrayList()
override fun getSpacing(child1: Block?, child2: Block): Spacing? = null
override fun isLeaf(): Boolean = false
}
private val builders = ArrayList<Builder>() private val builders = ArrayList<Builder>()
private interface Builder { private interface Builder {
@@ -89,12 +82,13 @@ class KotlinSpacingBuilder(val codeStyleSettings: CodeStyleSettings) {
fun emptyLinesIfLineBreakInLeft(emptyLines: Int, numberOfLineFeedsOtherwise: Int = 1, numSpacesOtherwise: Int = 0) { fun emptyLinesIfLineBreakInLeft(emptyLines: Int, numberOfLineFeedsOtherwise: Int = 1, numSpacesOtherwise: Int = 0) {
newRule { parent: ASTBlock, left: ASTBlock, right: ASTBlock -> newRule { parent: ASTBlock, left: ASTBlock, right: ASTBlock ->
val dependentSpacingRule = DependentSpacingRule(Trigger.HAS_LINE_FEEDS).registerData(Anchor.MIN_LINE_FEEDS, emptyLines + 1) val dependentSpacingRule = DependentSpacingRule(Trigger.HAS_LINE_FEEDS).registerData(Anchor.MIN_LINE_FEEDS, emptyLines + 1)
LineFeedDependantSpacing( spacingBuilderUtil.createLineFeedDependentSpacing(numSpacesOtherwise,
numSpacesOtherwise, numSpacesOtherwise, numSpacesOtherwise,
minimumLineFeeds = numberOfLineFeedsOtherwise, numberOfLineFeedsOtherwise,
keepLineBreaks = codeStyleSettings.KEEP_LINE_BREAKS, codeStyleSettings.KEEP_LINE_BREAKS,
keepBlankLines = codeStyleSettings.KEEP_BLANK_LINES_IN_DECLARATIONS, codeStyleSettings.KEEP_BLANK_LINES_IN_DECLARATIONS,
dependency = left.textRange, rule = dependentSpacingRule) left.textRange,
dependentSpacingRule)
} }
} }
@@ -145,24 +139,24 @@ class KotlinSpacingBuilder(val codeStyleSettings: CodeStyleSettings) {
builder.init() builder.init()
builders.add(builder) 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 { interface KotlinSpacingBuilderUtil {
val builder = KotlinSpacingBuilder(codeStyleSettings) 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() builder.init()
return builder return builder
} }
@@ -24,7 +24,6 @@ import com.intellij.openapi.util.TextRange
import com.intellij.formatting.ChildAttributes import com.intellij.formatting.ChildAttributes
import com.intellij.formatting.Spacing import com.intellij.formatting.Spacing
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import com.intellij.psi.formatter.common.AbstractBlock
import com.intellij.formatting.ASTBlock import com.intellij.formatting.ASTBlock
class SyntheticKotlinBlock( class SyntheticKotlinBlock(
@@ -33,7 +32,8 @@ class SyntheticKotlinBlock(
private val alignment: Alignment?, private val alignment: Alignment?,
private val indent: Indent?, private val indent: Indent?,
private val wrap: Wrap?, private val wrap: Wrap?,
private val spacingBuilder: KotlinSpacingBuilder private val spacingBuilder: KotlinSpacingBuilder,
private val createSyntheticSpacingNodeBlock: (ASTNode) -> ASTBlock
) : ASTBlock { ) : ASTBlock {
private val textRange = TextRange( private val textRange = TextRange(
@@ -49,8 +49,9 @@ class SyntheticKotlinBlock(
override fun isIncomplete() = getSubBlocks().last().isIncomplete override fun isIncomplete() = getSubBlocks().last().isIncomplete
override fun isLeaf() = false override fun isLeaf() = false
override fun getNode() = node override fun getNode() = node
override fun getSpacing(child1: Block?, child2: Block): Spacing? = override fun getSpacing(child1: Block?, child2: Block): Spacing? {
spacingBuilder.getSpacing(KotlinSpacingBuilder.SpacingNodeBlock(node.treeParent!!), child1, child2) return spacingBuilder.getSpacing(createSyntheticSpacingNodeBlock(node.treeParent!!), child1, child2)
}
override fun toString(): String { override fun toString(): String {
@@ -59,12 +60,10 @@ class SyntheticKotlinBlock(
loop@ loop@
while (treeNode == null) when (child) { while (treeNode == null) when (child) {
is AbstractBlock -> { is SyntheticKotlinBlock -> child = child.getSubBlocks().first()
treeNode = child.node
} is ASTBlock -> treeNode = child.node
is SyntheticKotlinBlock -> {
child = child.getSubBlocks().first()
}
else -> break@loop else -> break@loop
} }
@@ -81,4 +80,4 @@ class SyntheticKotlinBlock(
return javaClass.name + ": " + textRange return javaClass.name + ": " + textRange
} }
} }
@@ -22,8 +22,9 @@ import com.intellij.formatting.SpacingBuilder
import com.intellij.formatting.SpacingBuilder.RuleBuilder import com.intellij.formatting.SpacingBuilder.RuleBuilder
import com.intellij.lang.ASTNode import com.intellij.lang.ASTNode
import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.TokenType
import com.intellij.psi.codeStyle.CodeStyleSettings 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.IElementType
import com.intellij.psi.tree.TokenSet import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes.* 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() } 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 kotlinSettings = settings.getCustomSettings(KotlinCodeStyleSettings::class.java)!!
val kotlinCommonSettings = settings.getCommonSettings(KotlinLanguage.INSTANCE)!! val kotlinCommonSettings = settings.getCommonSettings(KotlinLanguage.INSTANCE)!!
return rules(settings) { return rules(settings, builderUtil) {
val DECLARATIONS = val DECLARATIONS =
TokenSet.create(PROPERTY, FUN, CLASS, OBJECT_DECLARATION, ENUM_ENTRY, SECONDARY_CONSTRUCTOR, CLASS_INITIALIZER) 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 = FUN, right = CLASS).emptyLinesIfLineBreakInLeft(1)
inPosition(left = ENUM_ENTRY, right = ENUM_ENTRY).emptyLinesIfLineBreakInLeft( 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 -> inPosition(parent = CLASS_BODY, left = SEMICOLON).customRule { parent, left, right ->
val klass = parent.node.treeParent.psi as? KtClass ?: return@customRule null 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 { inPosition(parent = parent, right = keyword).customRule {
parent, left, right -> parent, left, right ->
val previousLeaf = FormatterUtil.getPreviousNonWhitespaceLeaf(right.node) val previousLeaf = builderUtil.getPreviousNonWhitespaceLeaf(right.node)
val leftBlock = if ( val leftBlock = if (
previousLeaf != null && previousLeaf != null &&
previousLeaf.elementType == RBRACE && previousLeaf.elementType == RBRACE &&
@@ -277,7 +278,7 @@ fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder {
if (block != null && block.elementType == blockType) { if (block != null && block.elementType == blockType) {
val leftBrace = block.findChildByType(LBRACE) val leftBrace = block.findChildByType(LBRACE)
if (leftBrace != null) { if (leftBrace != null) {
val previousLeaf = FormatterUtil.getPreviousNonWhitespaceLeaf(leftBrace) val previousLeaf = builderUtil.getPreviousNonWhitespaceLeaf(leftBrace)
val isAfterEolComment = previousLeaf != null && (previousLeaf.elementType == EOL_COMMENT) val isAfterEolComment = previousLeaf != null && (previousLeaf.elementType == EOL_COMMENT)
val keepLineBreaks = kotlinSettings.LBRACE_ON_NEXT_LINE || isAfterEolComment val keepLineBreaks = kotlinSettings.LBRACE_ON_NEXT_LINE || isAfterEolComment
val minimumLF = if (kotlinSettings.LBRACE_ON_NEXT_LINE) 1 else 0 val minimumLF = if (kotlinSettings.LBRACE_ON_NEXT_LINE) 1 else 0
@@ -390,4 +391,4 @@ fun createSpacingBuilder(settings: CodeStyleSettings): KotlinSpacingBuilder {
after(EOL_COMMENT).lineBreakInCode() after(EOL_COMMENT).lineBreakInCode()
} }
} }
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.*
import com.intellij.formatting.alignment.AlignmentStrategy import com.intellij.formatting.alignment.AlignmentStrategy
import com.intellij.lang.ASTNode 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.codeStyle.CodeStyleSettings
import com.intellij.psi.formatter.FormatterUtil
import com.intellij.psi.formatter.common.AbstractBlock import com.intellij.psi.formatter.common.AbstractBlock
import com.intellij.psi.tree.IElementType 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.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.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 * @see Block for good JavaDoc documentation
*/ */
class KotlinBlock( class KotlinBlock(
node: ASTNode, node: ASTNode,
private val myAlignmentStrategy: NodeAlignmentStrategy, private val myAlignmentStrategy: CommonAlignmentStrategy,
private val myIndent: Indent?, private val myIndent: Indent?,
wrap: Wrap?, wrap: Wrap?,
private val mySettings: CodeStyleSettings, mySettings: CodeStyleSettings,
private val mySpacingBuilder: KotlinSpacingBuilder) : AbstractBlock(node, wrap, myAlignmentStrategy.getAlignment(node)) { private val mySpacingBuilder: KotlinSpacingBuilder) : AbstractBlock(node, wrap, myAlignmentStrategy.getAlignment(node)) {
private var mySubBlocks: List<Block>? = null private val kotlinDelegationBlock = object : KotlinCommonBlock(node, mySettings, mySpacingBuilder, myAlignmentStrategy) {
override fun getNullAlignmentStrategy(): CommonAlignmentStrategy = NodeAlignmentStrategy.getNullStrategy()
override fun getIndent(): Indent? { override fun createAlignmentStrategy(alignOption: Boolean, defaultAlignment: Alignment?): CommonAlignmentStrategy {
return myIndent return NodeAlignmentStrategy.fromTypes(AlignmentStrategy.wrap(createAlignment(alignOption, defaultAlignment)))
}
override fun buildChildren(): List<Block> {
if (mySubBlocks == null) {
var nodeSubBlocks = buildSubBlocks() as ArrayList<Block>
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<Block> {
val blocks = ArrayList<Block>()
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
} }
return blocks override fun getAlignmentForCaseBranch(shouldAlignInColumns: Boolean): CommonAlignmentStrategy {
} return if (shouldAlignInColumns) {
NodeAlignmentStrategy.fromTypes(
private fun buildSubBlock( AlignmentStrategy.createAlignmentPerTypeStrategy(listOf(ARROW as IElementType), WHEN_ENTRY, true))
child: ASTNode, }
alignmentStrategy: NodeAlignmentStrategy, else {
wrappingStrategy: WrappingStrategy): Block { NodeAlignmentStrategy.getNullStrategy()
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)
} }
} }
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<Block> = 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<Block> = emptyList()
}
}
} }
override fun getIndent(): Indent? = myIndent
override fun buildChildren(): List<Block> = kotlinDelegationBlock.buildChildren()
override fun getSpacing(child1: Block?, child2: Block): Spacing? = mySpacingBuilder.getSpacing(this, child1, child2) override fun getSpacing(child1: Block?, child2: Block): Spacing? = mySpacingBuilder.getSpacing(this, child1, child2)
override fun getChildAttributes(newChildIndex: Int): ChildAttributes { override fun getChildAttributes(newChildIndex: Int): ChildAttributes = kotlinDelegationBlock.getChildAttributes(newChildIndex)
val type = node.elementType
return when (type) {
in CODE_BLOCKS, WHEN, IF, FOR, WHILE, DO_WHILE -> ChildAttributes(Indent.getNormalIndent(), null)
TRY -> ChildAttributes(Indent.getNoneIndent(), null) override fun isLeaf(): Boolean = kotlinDelegationBlock.isLeaf()
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()
}
} }
private val INDENT_RULES = arrayOf<NodeIndentStrategy>( object KotlinSpacingBuilderUtilImpl : KotlinSpacingBuilderUtil {
strategy("No indent for braces in blocks") override fun getPreviousNonWhitespaceLeaf(node: ASTNode?): ASTNode? {
.`in`(BLOCK, CLASS_BODY, FUNCTION_LITERAL) return FormatterUtil.getPreviousNonWhitespaceLeaf(node)
.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
} }
return node override fun isWhitespaceOrEmpty(node: ASTNode?): Boolean {
} return FormatterUtil.isWhitespaceOrEmpty(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
} }
return node override fun createLineFeedDependentSpacing(
} minSpaces: Int,
maxSpaces: Int,
private fun getWrappingStrategyForItemList(wrapType: Int, itemType: IElementType): WrappingStrategy { minimumLineFeeds: Int,
val itemWrap = Wrap.createWrap(wrapType, false) keepLineBreaks: Boolean,
return object : WrappingStrategy { keepBlankLines: Int,
override fun getWrap(childElementType: IElementType): Wrap? { dependency: TextRange,
return if (childElementType === itemType) itemWrap else null 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
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
} }
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? { private fun createAlignment(alignOption: Boolean, defaultAlignment: Alignment?): Alignment? {
return if (alignOption) createAlignmentOrDefault(null, defaultAlignment) else defaultAlignment return if (alignOption) createAlignmentOrDefault(null, defaultAlignment) else defaultAlignment
} }
private fun createAlignmentOrDefault(base: Alignment?, defaultAlignment: Alignment?): Alignment? { private fun createAlignmentOrDefault(base: Alignment?, defaultAlignment: Alignment?): Alignment? {
return defaultAlignment ?: if (base == null) Alignment.createAlignment() else Alignment.createChildAlignment(base) return defaultAlignment ?: if (base == null) Alignment.createAlignment() else Alignment.createChildAlignment(base)
}
private fun findNodeBlockIndex(blocks: List<Block>, 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;
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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); PsiFile containingFile = element.getContainingFile().getViewProvider().getPsi(KotlinLanguage.INSTANCE);
KotlinBlock block = new KotlinBlock( KotlinBlock block = new KotlinBlock(
containingFile.getNode(), NodeAlignmentStrategy.getNullStrategy(), Indent.getNoneIndent(), null, settings, 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) //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 // it's needed until IDEA's issue with this document being created with wrong threading policy is fixed
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.NotNull;
import org.jetbrains.annotations.Nullable; 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)); private static final NodeAlignmentStrategy NULL_STRATEGY = fromTypes(AlignmentStrategy.wrap(null));
@@ -35,10 +35,11 @@ public abstract class NodeAlignmentStrategy {
return new AlignmentStrategyWrapper(strategy); return new AlignmentStrategyWrapper(strategy);
} }
@Override
@Nullable @Nullable
public abstract Alignment getAlignment(@NotNull ASTNode node); public abstract Alignment getAlignment(@NotNull ASTNode node);
public static class AlignmentStrategyWrapper extends NodeAlignmentStrategy { private static class AlignmentStrategyWrapper extends NodeAlignmentStrategy {
private final AlignmentStrategy internalStrategy; private final AlignmentStrategy internalStrategy;
public AlignmentStrategyWrapper(@NotNull AlignmentStrategy internalStrategy) { public AlignmentStrategyWrapper(@NotNull AlignmentStrategy internalStrategy) {
+2
View File
@@ -63,6 +63,7 @@
<root url="file://$PROJECT_DIR$/../out/production/util" /> <root url="file://$PROJECT_DIR$/../out/production/util" />
<root url="file://$PROJECT_DIR$/../out/production/util.runtime" /> <root url="file://$PROJECT_DIR$/../out/production/util.runtime" />
<root url="file://$PROJECT_DIR$/../out/production/Kotlin" /> <root url="file://$PROJECT_DIR$/../out/production/Kotlin" />
<root url="file://$PROJECT_DIR$/../out/production/formatter" />
</CLASSES> </CLASSES>
<JAVADOC /> <JAVADOC />
<SOURCES> <SOURCES>
@@ -94,6 +95,7 @@
<root url="file://$PROJECT_DIR$/../idea/idea-jps-common/src" /> <root url="file://$PROJECT_DIR$/../idea/idea-jps-common/src" />
<root url="file://$PROJECT_DIR$/../idea/idea-live-templates/src" /> <root url="file://$PROJECT_DIR$/../idea/idea-live-templates/src" />
<root url="file://$PROJECT_DIR$/../idea/idea-test-framework/src" /> <root url="file://$PROJECT_DIR$/../idea/idea-test-framework/src" />
<root url="file://$PROJECT_DIR$/../idea/formatter/src" />
</SOURCES> </SOURCES>
</library> </library>
</component> </component>