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:
Generated
+1
@@ -48,6 +48,7 @@
|
||||
<element id="module-output" name="idea-live-templates" />
|
||||
<element id="module-output" name="resolution" />
|
||||
<element id="module-output" name="idea-ultimate" />
|
||||
<element id="module-output" name="formatter" />
|
||||
</element>
|
||||
<element id="library" level="project" name="javax.inject" />
|
||||
<element id="directory" name="jps">
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-runtime" level="project" />
|
||||
<orderEntry type="library" name="idea-full" level="project" />
|
||||
<orderEntry type="module" module-name="idea-core" />
|
||||
<orderEntry type="module" module-name="frontend.java" />
|
||||
<orderEntry type="module" module-name="frontend" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../../ideaSDK/lib/openapi.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</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.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<Block>? = 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<Builder>()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Block>? = 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<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
|
||||
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<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 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<NodeIndentStrategy>(
|
||||
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<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");
|
||||
* 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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
<root url="file://$PROJECT_DIR$/../out/production/util" />
|
||||
<root url="file://$PROJECT_DIR$/../out/production/util.runtime" />
|
||||
<root url="file://$PROJECT_DIR$/../out/production/Kotlin" />
|
||||
<root url="file://$PROJECT_DIR$/../out/production/formatter" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
@@ -94,6 +95,7 @@
|
||||
<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-test-framework/src" />
|
||||
<root url="file://$PROJECT_DIR$/../idea/formatter/src" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Reference in New Issue
Block a user