KotlinLikeLangLineIndentProvider: cleanup code
Part of #KT-22211
This commit is contained in:
+412
-407
@@ -128,407 +128,6 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
|
||||
}
|
||||
}
|
||||
|
||||
private fun IndentCalculatorFactory.createIndentCalculatorForBrace(
|
||||
before: SemanticEditorPosition,
|
||||
after: SemanticEditorPosition,
|
||||
leftBraceType: SemanticEditorPosition.SyntaxElement,
|
||||
rightBraceType: SemanticEditorPosition.SyntaxElement
|
||||
): IndentCalculator? {
|
||||
val leftBrace = before.copyAnd {
|
||||
it.moveToLeftParenthesisBackwardsSkippingNested(leftBraceType, rightBraceType)
|
||||
}
|
||||
|
||||
val indent = if (after.after().afterOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET).isAt(Comma))
|
||||
createAlignMultilineIndent(leftBrace)
|
||||
else
|
||||
Indent.getNoneIndent()
|
||||
|
||||
return createIndentCalculator(indent, leftBrace.startOffset)
|
||||
}
|
||||
|
||||
private fun IndentCalculatorFactory.createIndentCalculatorForParenthesis(
|
||||
leftParenthesis: SemanticEditorPosition,
|
||||
currentPosition: SemanticEditorPosition,
|
||||
rightParenthesis: SemanticEditorPosition,
|
||||
offset: Int,
|
||||
settings: KotlinIndentationAdjuster,
|
||||
): IndentCalculator? {
|
||||
assert(leftParenthesis.isAt(LeftParenthesis))
|
||||
assert(rightParenthesis.isAt(RightParenthesis))
|
||||
|
||||
// case only for caret before [RightParenthesis]
|
||||
if (!currentPosition.hasLineBreaksAfter(offset)) {
|
||||
val indentForParentheses by lazy {
|
||||
if (settings.alignWhenMultilineFunctionParentheses) createAlignMultilineIndent(leftParenthesis) else Indent.getNoneIndent()
|
||||
}
|
||||
|
||||
findFunctionKeywordBeforeIdentifier(leftParenthesis.beforeIgnoringWhiteSpaceOrComment())?.let {
|
||||
return createIndentCalculator(indentForParentheses, it.startOffset)
|
||||
}
|
||||
|
||||
// NB: this covered [KtTokens.CONSTRUCTOR_KEYWORD], [KtTokens.SET_KEYWORD], [KtTokens.GET_KEYWORD], [KtTokens.INIT_KEYWORD] as well
|
||||
if (isSimilarToFunctionInvocation(leftParenthesis)) {
|
||||
return createIndentCalculator(indentForParentheses, leftParenthesis.startOffset)
|
||||
}
|
||||
|
||||
if (isDestructuringDeclaration(leftParenthesis, rightParenthesis)) {
|
||||
return createIndentCalculator(Indent.getNoneIndent(), leftParenthesis.startOffset)
|
||||
}
|
||||
|
||||
leftParenthesis.beforeIgnoringWhiteSpaceOrComment().let { keyword ->
|
||||
if (keyword.isControlFlowKeyword()) {
|
||||
return createIndentCalculator(Indent.getNoneIndent(), keyword.startOffset)
|
||||
}
|
||||
}
|
||||
|
||||
val indentForBinaryExpression = if (settings.alignWhenMultilineBinaryExpression)
|
||||
createAlignMultilineIndent(leftParenthesis)
|
||||
else
|
||||
Indent.getContinuationIndent()
|
||||
|
||||
return createIndentCalculator(indentForBinaryExpression, leftParenthesis.startOffset)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @receiver is position before '=' for expression body or '{' for block body
|
||||
*/
|
||||
private fun SemanticEditorPosition.isFunctionDeclaration(): Boolean = findFunctionDeclarationBeforeBody(this) != null
|
||||
|
||||
/**
|
||||
* @param endOfDeclaration is position before '=' for expression body or '{' for block body
|
||||
*/
|
||||
private fun findFunctionOrPropertyOrMultiDeclarationBefore(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? =
|
||||
findFunctionDeclarationBeforeBody(endOfDeclaration)
|
||||
?: findPropertyDeclarationBeforeAssignment(endOfDeclaration)
|
||||
?: findMultiDeclarationBeforeAssignment(endOfDeclaration)
|
||||
|
||||
/**
|
||||
* `val (a, b) = 1 to 2`
|
||||
* ^
|
||||
*/
|
||||
private fun findMultiDeclarationBeforeAssignment(rightParenthesis: SemanticEditorPosition): SemanticEditorPosition? {
|
||||
if (!rightParenthesis.isAt(RightParenthesis)) return null
|
||||
return with(rightParenthesis.copy()) {
|
||||
if (!moveBeforeParenthesesIfPossible()) return null
|
||||
takeIf { isVarOrVal() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `val a = 5`
|
||||
* `val a: Int = 5`
|
||||
* `val List<Int>.a: Int = 5`
|
||||
* `val <T> List<Int>.a: Int = 5`
|
||||
*/
|
||||
private fun findPropertyDeclarationBeforeAssignment(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? {
|
||||
// `val a = 5`
|
||||
// this is a false positive for a declaration with explicit return type
|
||||
if (endOfDeclaration.isAt(Identifier)) {
|
||||
findPropertyKeywordBeforeIdentifier(endOfDeclaration)?.let { return it }
|
||||
}
|
||||
|
||||
return with(endOfDeclaration.copy()) {
|
||||
// explicit type `fun a(): String` or `val a: String`
|
||||
if (moveBeforeTypeQualifierIfPossible(true)) {
|
||||
if (!isAt(Colon)) return null
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
findPropertyKeywordBeforeIdentifier(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `val a = fun() { }`
|
||||
* `val a = fun String.Companion????.() { }`
|
||||
* `fun a() = 4`
|
||||
* `fun a(): Int = 4`
|
||||
* `fun Int.a(): Int = 4`
|
||||
* `fun <T> T.a(): Int = 4`
|
||||
*/
|
||||
private fun findFunctionDeclarationBeforeBody(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? =
|
||||
with(endOfDeclaration.copy()) {
|
||||
// explicit type `fun a(): String`
|
||||
// ^
|
||||
if (moveBeforeTypeQualifierIfPossible(true)) {
|
||||
if (!isAt(Colon)) return null
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
if (!moveBeforeParenthesesIfPossible()) return null
|
||||
|
||||
findFunctionKeywordBeforeIdentifier(this)
|
||||
}
|
||||
|
||||
private fun findPropertyKeywordBeforeIdentifier(identifierPosition: SemanticEditorPosition): SemanticEditorPosition? {
|
||||
if (!identifierPosition.isAt(Identifier)) return null
|
||||
return with(identifierPosition.copy()) {
|
||||
if (!moveBeforeTypeQualifierIfPossible(false)) return null
|
||||
// `val <T> List<T>.prop`
|
||||
moveBeforeTypeParametersIfPossible()
|
||||
takeIf { it.isVarOrVal() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return position of `fun` keyword before the declaration or null
|
||||
* TODO: support [KtTokens.CONSTRUCTOR_KEYWORD], [KtTokens.INIT_KEYWORD]. Maybe [KtTokens.SET_KEYWORD], [KtTokens.GET_KEYWORD] (related to KT-39444)
|
||||
*/
|
||||
private fun findFunctionKeywordBeforeIdentifier(identifierPosition: SemanticEditorPosition): SemanticEditorPosition? {
|
||||
// anonymous function `val a = fun() { }`
|
||||
// ^
|
||||
if (identifierPosition.isAt(FunctionKeyword)) return identifierPosition
|
||||
|
||||
return with(identifierPosition.copy()) {
|
||||
moveBeforeWhileThisIsWhiteSpaceOrComment()
|
||||
|
||||
// anonymous function with receiver `val a = fun String.Companion????.() { }`
|
||||
// ^
|
||||
if (isAt(Dot)) {
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
if (!moveBeforeTypeQualifierIfPossible(true)) return null
|
||||
return if (isAt(FunctionKeyword)) this else null
|
||||
}
|
||||
|
||||
// name of declaration
|
||||
if (!isAt(Identifier)) return null
|
||||
if (!moveBeforeTypeQualifierIfPossible(false)) return null
|
||||
|
||||
moveBeforeTypeParametersIfPossible()
|
||||
|
||||
takeIf { it.isAt(FunctionKeyword) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param leftParenthesis is `(` or `[`
|
||||
*/
|
||||
private fun isSimilarToFunctionInvocation(leftParenthesis: SemanticEditorPosition): Boolean = with(leftParenthesis.copy()) {
|
||||
moveBefore()
|
||||
|
||||
if (!moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment() || isAtEnd) return false
|
||||
|
||||
// calls with types e.g. `test<Int>()`
|
||||
if (isAt(CloseTypeBrace)) {
|
||||
moveBeforeParentheses(OpenTypeBrace, CloseTypeBrace)
|
||||
return moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment() && isIdentifier()
|
||||
}
|
||||
|
||||
return isIdentifier() || isAtAnyOf(RightParenthesis, ArrayClosingBracket)
|
||||
}
|
||||
|
||||
private fun isDestructuringDeclaration(
|
||||
leftParenthesis: SemanticEditorPosition,
|
||||
rightParenthesis: SemanticEditorPosition
|
||||
): Boolean = with(leftParenthesis.copy()) {
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
|
||||
// val (a, b) = 1 to 2
|
||||
if (isVarOrVal()) return true
|
||||
|
||||
// in lambda like `val a = { i: Int -> println(i) }`
|
||||
if (!rightParenthesis.moveBeforeParametersIfPossible()) return false
|
||||
|
||||
return isAt(BlockOpeningBrace)
|
||||
}
|
||||
|
||||
/**
|
||||
* @receiver start position of any parameter
|
||||
*/
|
||||
private fun SemanticEditorPosition.moveBeforeParametersIfPossible(): Boolean {
|
||||
while (!isAtEnd) {
|
||||
if (!moveBeforeParameterIfPossible()) return false
|
||||
if (!isAt(Comma)) return true
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @receiver start position of any parameter
|
||||
*/
|
||||
private fun SemanticEditorPosition.moveBeforeParameterIfPossible(): Boolean {
|
||||
// destructuring declaration in lambda parameters without type
|
||||
// { a, (b, c), d ->
|
||||
// ^
|
||||
if (isAt(RightParenthesis)) return moveBeforeParenthesesIfPossible()
|
||||
|
||||
if (!moveBeforeTypeQualifierIfPossible(true)) return false
|
||||
|
||||
// optional colon
|
||||
// { a: Int ->
|
||||
// ^
|
||||
// (a: Int)
|
||||
// ^
|
||||
if (isAt(Colon)) {
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
|
||||
// destructuring declaration in lambda parameters with type
|
||||
// { a, b, (c, d): Pair<Long, Long>, e ->
|
||||
// ^
|
||||
if (isAt(RightParenthesis)) return moveBeforeParenthesesIfPossible()
|
||||
|
||||
// { a: Int ->
|
||||
// ^
|
||||
// (a: Int)
|
||||
// ^
|
||||
if (!isAt(Identifier)) return false
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/***
|
||||
* Constructions like `OuterClass/* */.MiddleClass<String>?????`, `String.Companion.testName`
|
||||
*
|
||||
* @receiver position of identifier
|
||||
*/
|
||||
private fun SemanticEditorPosition.moveBeforeTypeQualifierIfPossible(canStartWithTypeParameter: Boolean): Boolean {
|
||||
if (!canStartWithTypeParameter && !isAt(Identifier)) return false
|
||||
|
||||
while (!isAtEnd) {
|
||||
moveBeforeOptionalMix(Quest, *WHITE_SPACE_OR_COMMENT_BIT_SET)
|
||||
moveBeforeTypeParametersIfPossible()
|
||||
if (!isAt(Identifier)) return false
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
if (!isAt(Dot)) return true
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun createAlignMultilineIndent(position: SemanticEditorPosition): Indent {
|
||||
val beforeLineStart = CharArrayUtil.shiftBackwardUntil(position.chars, position.startOffset, "\n") + 1
|
||||
val beforeLineWithoutIndentStart = CharArrayUtil.shiftForward(position.chars, beforeLineStart, " \t")
|
||||
return Indent.getSpaceIndent(position.startOffset - beforeLineWithoutIndentStart)
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.isWhileInsideDoWhile(): Boolean {
|
||||
if (!isAt(WhileKeyword)) return false
|
||||
with(copy()) {
|
||||
moveBefore()
|
||||
var whileKeywordLevel = 1
|
||||
while (!isAtEnd) when {
|
||||
isAt(BlockOpeningBrace) -> return false
|
||||
isAt(DoKeyword) -> {
|
||||
if (--whileKeywordLevel == 0) return true
|
||||
moveBefore()
|
||||
}
|
||||
isAt(WhileKeyword) -> {
|
||||
++whileKeywordLevel
|
||||
moveBefore()
|
||||
}
|
||||
isAt(BlockClosingBrace) -> moveBeforeParentheses(BlockOpeningBrace, BlockClosingBrace)
|
||||
else -> moveBefore()
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.controlFlowStatementBefore(): SemanticEditorPosition? = with(copy()) {
|
||||
if (isAt(BlockOpeningBrace)) moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
|
||||
if (isControlFlowKeyword()) return this
|
||||
if (!moveBeforeParenthesesIfPossible()) return null
|
||||
|
||||
return takeIf { isControlFlowKeyword() }
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.isControlFlowKeyword(): Boolean =
|
||||
currElement in CONTROL_FLOW_KEYWORDS || isCatchKeyword() || isFinallyKeyword()
|
||||
|
||||
private fun SemanticEditorPosition.similarToCatchKeyword(): Boolean = textOfCurrentPosition() == KtTokens.CATCH_KEYWORD.value
|
||||
|
||||
private fun SemanticEditorPosition.isCatchKeyword(): Boolean = with(copy()) {
|
||||
// try-catch-*-catch
|
||||
do {
|
||||
if (!isAt(Identifier)) return false
|
||||
if (!similarToCatchKeyword()) return false
|
||||
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
if (!moveBeforeBlockIfPossible()) return false
|
||||
|
||||
if (isAt(TryKeyword)) return true
|
||||
|
||||
if (!moveBeforeParenthesesIfPossible()) return false
|
||||
} while (!isAtEnd)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.isFinallyKeyword(): Boolean {
|
||||
if (!isAt(Identifier)) return false
|
||||
if (textOfCurrentPosition() != KtTokens.FINALLY_KEYWORD.value) return false
|
||||
with(copy()) {
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
if (!moveBeforeBlockIfPossible()) return false
|
||||
|
||||
// try-finally
|
||||
if (isAt(TryKeyword)) return true
|
||||
|
||||
if (!moveBeforeParenthesesIfPossible()) return false
|
||||
|
||||
// try-catch-finally
|
||||
return isCatchKeyword()
|
||||
}
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.isIdentifier(): Boolean = isAt(Identifier) || isAt(KtTokens.THIS_KEYWORD)
|
||||
|
||||
private fun SemanticEditorPosition.isVarOrVal(): Boolean = isAtAnyOf(Var, Val)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeBlockIfPossible(): Boolean = moveBeforeParenthesesIfPossible(
|
||||
leftParenthesis = BlockOpeningBrace,
|
||||
rightParenthesis = BlockClosingBrace,
|
||||
)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeTypeParametersIfPossible(): Boolean = moveBeforeParenthesesIfPossible(
|
||||
leftParenthesis = OpenTypeBrace,
|
||||
rightParenthesis = CloseTypeBrace,
|
||||
)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeParenthesesIfPossible(): Boolean = moveBeforeParenthesesIfPossible(
|
||||
leftParenthesis = LeftParenthesis,
|
||||
rightParenthesis = RightParenthesis,
|
||||
)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeParenthesesIfPossible(
|
||||
leftParenthesis: SemanticEditorPosition.SyntaxElement,
|
||||
rightParenthesis: SemanticEditorPosition.SyntaxElement,
|
||||
): Boolean {
|
||||
if (!isAt(rightParenthesis)) return false
|
||||
|
||||
moveBeforeParentheses(leftParenthesis, rightParenthesis)
|
||||
moveBeforeWhileThisIsWhiteSpaceOrComment()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeWhileThisIsWhiteSpaceOrComment() = moveBeforeOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment(): Boolean {
|
||||
while (!isAtEnd) {
|
||||
if (isAt(Whitespace) && isAtMultiline) return false
|
||||
if (!isAt(BlockComment)) break
|
||||
moveBefore()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeIgnoringWhiteSpaceOrComment() {
|
||||
moveBefore()
|
||||
moveBeforeWhileThisIsWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.beforeIgnoringWhiteSpaceOrComment() = copyAnd { it.moveBeforeIgnoringWhiteSpaceOrComment() }
|
||||
|
||||
private enum class KotlinElement : SemanticEditorPosition.SyntaxElement {
|
||||
TemplateEntryOpen,
|
||||
TemplateEntryClose,
|
||||
@@ -552,16 +151,26 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
|
||||
companion object {
|
||||
private val SYNTAX_MAP: Map<IElementType, SemanticEditorPosition.SyntaxElement> = hashMapOf(
|
||||
KtTokens.WHITE_SPACE to Whitespace,
|
||||
KtTokens.LONG_TEMPLATE_ENTRY_START to TemplateEntryOpen,
|
||||
KtTokens.LONG_TEMPLATE_ENTRY_END to TemplateEntryClose,
|
||||
KtTokens.EOL_COMMENT to LineComment,
|
||||
KtTokens.BLOCK_COMMENT to BlockComment,
|
||||
KtTokens.DOC_COMMENT to KDoc,
|
||||
KtTokens.ARROW to Arrow,
|
||||
|
||||
KtTokens.LONG_TEMPLATE_ENTRY_START to TemplateEntryOpen,
|
||||
KtTokens.LONG_TEMPLATE_ENTRY_END to TemplateEntryClose,
|
||||
|
||||
KtTokens.LBRACE to BlockOpeningBrace,
|
||||
KtTokens.RBRACE to BlockClosingBrace,
|
||||
|
||||
KtTokens.LPAR to LeftParenthesis,
|
||||
KtTokens.RPAR to RightParenthesis,
|
||||
|
||||
KtTokens.LBRACKET to ArrayOpeningBracket,
|
||||
KtTokens.RBRACKET to ArrayClosingBracket,
|
||||
|
||||
KtTokens.LT to OpenTypeBrace,
|
||||
KtTokens.GT to CloseTypeBrace,
|
||||
|
||||
KtTokens.IF_KEYWORD to IfKeyword,
|
||||
KtTokens.ELSE_KEYWORD to ElseKeyword,
|
||||
KtTokens.WHEN_KEYWORD to WhenKeyword,
|
||||
@@ -569,12 +178,9 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
|
||||
KtTokens.WHILE_KEYWORD to WhileKeyword,
|
||||
KtTokens.DO_KEYWORD to DoKeyword,
|
||||
KtTokens.FOR_KEYWORD to ForKeyword,
|
||||
|
||||
KtTokens.REGULAR_STRING_PART to RegularStringPart,
|
||||
KtTokens.LBRACKET to ArrayOpeningBracket,
|
||||
KtTokens.RBRACKET to ArrayClosingBracket,
|
||||
KtTokens.IDENTIFIER to Identifier,
|
||||
KtTokens.LT to OpenTypeBrace,
|
||||
KtTokens.GT to CloseTypeBrace,
|
||||
KtTokens.FUN_KEYWORD to FunctionKeyword,
|
||||
KtTokens.DOT to Dot,
|
||||
KtTokens.QUEST to Quest,
|
||||
@@ -602,6 +208,405 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
|
||||
LineComment,
|
||||
BlockComment,
|
||||
)
|
||||
|
||||
private fun IndentCalculatorFactory.createIndentCalculatorForBrace(
|
||||
before: SemanticEditorPosition,
|
||||
after: SemanticEditorPosition,
|
||||
leftBraceType: SemanticEditorPosition.SyntaxElement,
|
||||
rightBraceType: SemanticEditorPosition.SyntaxElement
|
||||
): IndentCalculator? {
|
||||
val leftBrace = before.copyAnd {
|
||||
it.moveToLeftParenthesisBackwardsSkippingNested(leftBraceType, rightBraceType)
|
||||
}
|
||||
|
||||
val indent = if (after.after().afterOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET).isAt(Comma))
|
||||
createAlignMultilineIndent(leftBrace)
|
||||
else
|
||||
Indent.getNoneIndent()
|
||||
|
||||
return createIndentCalculator(indent, leftBrace.startOffset)
|
||||
}
|
||||
|
||||
private fun IndentCalculatorFactory.createIndentCalculatorForParenthesis(
|
||||
leftParenthesis: SemanticEditorPosition,
|
||||
currentPosition: SemanticEditorPosition,
|
||||
rightParenthesis: SemanticEditorPosition,
|
||||
offset: Int,
|
||||
settings: KotlinIndentationAdjuster,
|
||||
): IndentCalculator? {
|
||||
assert(leftParenthesis.isAt(LeftParenthesis))
|
||||
assert(rightParenthesis.isAt(RightParenthesis))
|
||||
|
||||
// case only for caret before [RightParenthesis]
|
||||
if (!currentPosition.hasLineBreaksAfter(offset)) {
|
||||
val indentForParentheses by lazy {
|
||||
if (settings.alignWhenMultilineFunctionParentheses) createAlignMultilineIndent(leftParenthesis) else Indent.getNoneIndent()
|
||||
}
|
||||
|
||||
findFunctionKeywordBeforeIdentifier(leftParenthesis.beforeIgnoringWhiteSpaceOrComment())?.let {
|
||||
return createIndentCalculator(indentForParentheses, it.startOffset)
|
||||
}
|
||||
|
||||
// NB: this covered [KtTokens.CONSTRUCTOR_KEYWORD], [KtTokens.SET_KEYWORD], [KtTokens.GET_KEYWORD], [KtTokens.INIT_KEYWORD] as well
|
||||
if (isSimilarToFunctionInvocation(leftParenthesis)) {
|
||||
return createIndentCalculator(indentForParentheses, leftParenthesis.startOffset)
|
||||
}
|
||||
|
||||
if (isDestructuringDeclaration(leftParenthesis, rightParenthesis)) {
|
||||
return createIndentCalculator(Indent.getNoneIndent(), leftParenthesis.startOffset)
|
||||
}
|
||||
|
||||
leftParenthesis.beforeIgnoringWhiteSpaceOrComment().let { keyword ->
|
||||
if (keyword.isControlFlowKeyword()) {
|
||||
return createIndentCalculator(Indent.getNoneIndent(), keyword.startOffset)
|
||||
}
|
||||
}
|
||||
|
||||
val indentForBinaryExpression = if (settings.alignWhenMultilineBinaryExpression)
|
||||
createAlignMultilineIndent(leftParenthesis)
|
||||
else
|
||||
Indent.getContinuationIndent()
|
||||
|
||||
return createIndentCalculator(indentForBinaryExpression, leftParenthesis.startOffset)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @receiver is position before '=' for expression body or '{' for block body
|
||||
*/
|
||||
private fun SemanticEditorPosition.isFunctionDeclaration(): Boolean = findFunctionDeclarationBeforeBody(this) != null
|
||||
|
||||
/**
|
||||
* @param endOfDeclaration is position before '=' for expression body or '{' for block body
|
||||
*/
|
||||
private fun findFunctionOrPropertyOrMultiDeclarationBefore(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? =
|
||||
findFunctionDeclarationBeforeBody(endOfDeclaration)
|
||||
?: findPropertyDeclarationBeforeAssignment(endOfDeclaration)
|
||||
?: findMultiDeclarationBeforeAssignment(endOfDeclaration)
|
||||
|
||||
/**
|
||||
* `val (a, b) = 1 to 2`
|
||||
* ^
|
||||
*/
|
||||
private fun findMultiDeclarationBeforeAssignment(rightParenthesis: SemanticEditorPosition): SemanticEditorPosition? {
|
||||
if (!rightParenthesis.isAt(RightParenthesis)) return null
|
||||
return with(rightParenthesis.copy()) {
|
||||
if (!moveBeforeParenthesesIfPossible()) return null
|
||||
takeIf { isVarOrVal() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `val a = 5`
|
||||
* `val a: Int = 5`
|
||||
* `val List<Int>.a: Int = 5`
|
||||
* `val <T> List<Int>.a: Int = 5`
|
||||
*/
|
||||
private fun findPropertyDeclarationBeforeAssignment(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? {
|
||||
// `val a = 5`
|
||||
// this is a false positive for a declaration with explicit return type
|
||||
if (endOfDeclaration.isAt(Identifier)) {
|
||||
findPropertyKeywordBeforeIdentifier(endOfDeclaration)?.let { return it }
|
||||
}
|
||||
|
||||
return with(endOfDeclaration.copy()) {
|
||||
// explicit type `fun a(): String` or `val a: String`
|
||||
if (moveBeforeTypeQualifierIfPossible(true)) {
|
||||
if (!isAt(Colon)) return null
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
findPropertyKeywordBeforeIdentifier(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `val a = fun() { }`
|
||||
* `val a = fun String.Companion????.() { }`
|
||||
* `fun a() = 4`
|
||||
* `fun a(): Int = 4`
|
||||
* `fun Int.a(): Int = 4`
|
||||
* `fun <T> T.a(): Int = 4`
|
||||
*/
|
||||
private fun findFunctionDeclarationBeforeBody(endOfDeclaration: SemanticEditorPosition): SemanticEditorPosition? =
|
||||
with(endOfDeclaration.copy()) {
|
||||
// explicit type `fun a(): String`
|
||||
// ^
|
||||
if (moveBeforeTypeQualifierIfPossible(true)) {
|
||||
if (!isAt(Colon)) return null
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
if (!moveBeforeParenthesesIfPossible()) return null
|
||||
|
||||
findFunctionKeywordBeforeIdentifier(this)
|
||||
}
|
||||
|
||||
private fun findPropertyKeywordBeforeIdentifier(identifierPosition: SemanticEditorPosition): SemanticEditorPosition? {
|
||||
if (!identifierPosition.isAt(Identifier)) return null
|
||||
return with(identifierPosition.copy()) {
|
||||
if (!moveBeforeTypeQualifierIfPossible(false)) return null
|
||||
// `val <T> List<T>.prop`
|
||||
moveBeforeTypeParametersIfPossible()
|
||||
takeIf { it.isVarOrVal() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return position of `fun` keyword before the declaration or null
|
||||
* TODO: support [KtTokens.CONSTRUCTOR_KEYWORD], [KtTokens.INIT_KEYWORD]. Maybe [KtTokens.SET_KEYWORD], [KtTokens.GET_KEYWORD] (related to KT-39444)
|
||||
*/
|
||||
private fun findFunctionKeywordBeforeIdentifier(identifierPosition: SemanticEditorPosition): SemanticEditorPosition? {
|
||||
// anonymous function `val a = fun() { }`
|
||||
// ^
|
||||
if (identifierPosition.isAt(FunctionKeyword)) return identifierPosition
|
||||
|
||||
return with(identifierPosition.copy()) {
|
||||
moveBeforeWhileThisIsWhiteSpaceOrComment()
|
||||
|
||||
// anonymous function with receiver `val a = fun String.Companion????.() { }`
|
||||
// ^
|
||||
if (isAt(Dot)) {
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
if (!moveBeforeTypeQualifierIfPossible(true)) return null
|
||||
return if (isAt(FunctionKeyword)) this else null
|
||||
}
|
||||
|
||||
// name of declaration
|
||||
if (!isAt(Identifier)) return null
|
||||
if (!moveBeforeTypeQualifierIfPossible(false)) return null
|
||||
|
||||
moveBeforeTypeParametersIfPossible()
|
||||
|
||||
takeIf { it.isAt(FunctionKeyword) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param leftParenthesis is `(` or `[`
|
||||
*/
|
||||
private fun isSimilarToFunctionInvocation(leftParenthesis: SemanticEditorPosition): Boolean = with(leftParenthesis.copy()) {
|
||||
moveBefore()
|
||||
|
||||
if (!moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment() || isAtEnd) return false
|
||||
|
||||
// calls with types e.g. `test<Int>()`
|
||||
if (isAt(CloseTypeBrace)) {
|
||||
moveBeforeParentheses(OpenTypeBrace, CloseTypeBrace)
|
||||
return moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment() && isIdentifier()
|
||||
}
|
||||
|
||||
return isIdentifier() || isAtAnyOf(RightParenthesis, ArrayClosingBracket)
|
||||
}
|
||||
|
||||
private fun isDestructuringDeclaration(
|
||||
leftParenthesis: SemanticEditorPosition,
|
||||
rightParenthesis: SemanticEditorPosition
|
||||
): Boolean = with(leftParenthesis.copy()) {
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
|
||||
// val (a, b) = 1 to 2
|
||||
if (isVarOrVal()) return true
|
||||
|
||||
// in lambda like `val a = { i: Int -> println(i) }`
|
||||
if (!rightParenthesis.moveBeforeParametersIfPossible()) return false
|
||||
|
||||
return isAt(BlockOpeningBrace)
|
||||
}
|
||||
|
||||
/**
|
||||
* @receiver start position of any parameter
|
||||
*/
|
||||
private fun SemanticEditorPosition.moveBeforeParametersIfPossible(): Boolean {
|
||||
while (!isAtEnd) {
|
||||
if (!moveBeforeParameterIfPossible()) return false
|
||||
if (!isAt(Comma)) return true
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @receiver start position of any parameter
|
||||
*/
|
||||
private fun SemanticEditorPosition.moveBeforeParameterIfPossible(): Boolean {
|
||||
// destructuring declaration in lambda parameters without type
|
||||
// { a, (b, c), d ->
|
||||
// ^
|
||||
if (isAt(RightParenthesis)) return moveBeforeParenthesesIfPossible()
|
||||
|
||||
if (!moveBeforeTypeQualifierIfPossible(true)) return false
|
||||
|
||||
// optional colon
|
||||
// { a: Int ->
|
||||
// ^
|
||||
// (a: Int)
|
||||
// ^
|
||||
if (isAt(Colon)) {
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
|
||||
// destructuring declaration in lambda parameters with type
|
||||
// { a, b, (c, d): Pair<Long, Long>, e ->
|
||||
// ^
|
||||
if (isAt(RightParenthesis)) return moveBeforeParenthesesIfPossible()
|
||||
|
||||
// { a: Int ->
|
||||
// ^
|
||||
// (a: Int)
|
||||
// ^
|
||||
if (!isAt(Identifier)) return false
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/***
|
||||
* Constructions like `OuterClass/* */.MiddleClass<String>?????`, `String.Companion.testName`
|
||||
*
|
||||
* @receiver position of identifier
|
||||
*/
|
||||
private fun SemanticEditorPosition.moveBeforeTypeQualifierIfPossible(canStartWithTypeParameter: Boolean): Boolean {
|
||||
if (!canStartWithTypeParameter && !isAt(Identifier)) return false
|
||||
|
||||
while (!isAtEnd) {
|
||||
moveBeforeOptionalMix(Quest, *WHITE_SPACE_OR_COMMENT_BIT_SET)
|
||||
moveBeforeTypeParametersIfPossible()
|
||||
if (!isAt(Identifier)) return false
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
if (!isAt(Dot)) return true
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun createAlignMultilineIndent(position: SemanticEditorPosition): Indent {
|
||||
val beforeLineStart = CharArrayUtil.shiftBackwardUntil(position.chars, position.startOffset, "\n") + 1
|
||||
val beforeLineWithoutIndentStart = CharArrayUtil.shiftForward(position.chars, beforeLineStart, " \t")
|
||||
return Indent.getSpaceIndent(position.startOffset - beforeLineWithoutIndentStart)
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.isWhileInsideDoWhile(): Boolean {
|
||||
if (!isAt(WhileKeyword)) return false
|
||||
with(copy()) {
|
||||
moveBefore()
|
||||
var whileKeywordLevel = 1
|
||||
while (!isAtEnd) when {
|
||||
isAt(BlockOpeningBrace) -> return false
|
||||
isAt(DoKeyword) -> {
|
||||
if (--whileKeywordLevel == 0) return true
|
||||
moveBefore()
|
||||
}
|
||||
isAt(WhileKeyword) -> {
|
||||
++whileKeywordLevel
|
||||
moveBefore()
|
||||
}
|
||||
isAt(BlockClosingBrace) -> moveBeforeParentheses(BlockOpeningBrace, BlockClosingBrace)
|
||||
else -> moveBefore()
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.controlFlowStatementBefore(): SemanticEditorPosition? = with(copy()) {
|
||||
if (isAt(BlockOpeningBrace)) moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
|
||||
if (isControlFlowKeyword()) return this
|
||||
if (!moveBeforeParenthesesIfPossible()) return null
|
||||
|
||||
return takeIf { isControlFlowKeyword() }
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.isControlFlowKeyword(): Boolean =
|
||||
currElement in CONTROL_FLOW_KEYWORDS || isCatchKeyword() || isFinallyKeyword()
|
||||
|
||||
private fun SemanticEditorPosition.similarToCatchKeyword(): Boolean = textOfCurrentPosition() == KtTokens.CATCH_KEYWORD.value
|
||||
|
||||
private fun SemanticEditorPosition.isCatchKeyword(): Boolean = with(copy()) {
|
||||
// try-catch-*-catch
|
||||
do {
|
||||
if (!isAt(Identifier)) return false
|
||||
if (!similarToCatchKeyword()) return false
|
||||
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
if (!moveBeforeBlockIfPossible()) return false
|
||||
|
||||
if (isAt(TryKeyword)) return true
|
||||
|
||||
if (!moveBeforeParenthesesIfPossible()) return false
|
||||
} while (!isAtEnd)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.isFinallyKeyword(): Boolean {
|
||||
if (!isAt(Identifier)) return false
|
||||
if (textOfCurrentPosition() != KtTokens.FINALLY_KEYWORD.value) return false
|
||||
with(copy()) {
|
||||
moveBeforeIgnoringWhiteSpaceOrComment()
|
||||
if (!moveBeforeBlockIfPossible()) return false
|
||||
|
||||
// try-finally
|
||||
if (isAt(TryKeyword)) return true
|
||||
|
||||
if (!moveBeforeParenthesesIfPossible()) return false
|
||||
|
||||
// try-catch-finally
|
||||
return isCatchKeyword()
|
||||
}
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeWhileThisIsWhiteSpaceOrComment() =
|
||||
moveBeforeOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeIgnoringWhiteSpaceOrComment() {
|
||||
moveBefore()
|
||||
moveBeforeWhileThisIsWhiteSpaceOrComment()
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.beforeIgnoringWhiteSpaceOrComment() = copyAnd { it.moveBeforeIgnoringWhiteSpaceOrComment() }
|
||||
private fun SemanticEditorPosition.moveBeforeWhileThisIsWhiteSpaceOnSameLineOrBlockComment(): Boolean {
|
||||
while (!isAtEnd) {
|
||||
if (isAt(Whitespace) && isAtMultiline) return false
|
||||
if (!isAt(BlockComment)) break
|
||||
moveBefore()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun SemanticEditorPosition.isIdentifier(): Boolean = isAt(Identifier) || isAt(KtTokens.THIS_KEYWORD)
|
||||
private fun SemanticEditorPosition.isVarOrVal(): Boolean = isAtAnyOf(Var, Val)
|
||||
private fun SemanticEditorPosition.moveBeforeBlockIfPossible(): Boolean = moveBeforeParenthesesIfPossible(
|
||||
leftParenthesis = BlockOpeningBrace,
|
||||
rightParenthesis = BlockClosingBrace,
|
||||
)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeTypeParametersIfPossible(): Boolean = moveBeforeParenthesesIfPossible(
|
||||
leftParenthesis = OpenTypeBrace,
|
||||
rightParenthesis = CloseTypeBrace,
|
||||
)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeParenthesesIfPossible(): Boolean = moveBeforeParenthesesIfPossible(
|
||||
leftParenthesis = LeftParenthesis,
|
||||
rightParenthesis = RightParenthesis,
|
||||
)
|
||||
|
||||
private fun SemanticEditorPosition.moveBeforeParenthesesIfPossible(
|
||||
leftParenthesis: SemanticEditorPosition.SyntaxElement,
|
||||
rightParenthesis: SemanticEditorPosition.SyntaxElement,
|
||||
): Boolean {
|
||||
if (!isAt(rightParenthesis)) return false
|
||||
|
||||
moveBeforeParentheses(leftParenthesis, rightParenthesis)
|
||||
moveBeforeWhileThisIsWhiteSpaceOrComment()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user