KotlinLikeLangLineIndentProvider: cleanup code

Part of #KT-22211
This commit is contained in:
Dmitry Gridin
2020-06-14 14:21:25 +07:00
parent 01707800c1
commit c95216dc5d
@@ -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 { private enum class KotlinElement : SemanticEditorPosition.SyntaxElement {
TemplateEntryOpen, TemplateEntryOpen,
TemplateEntryClose, TemplateEntryClose,
@@ -552,16 +151,26 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
companion object { companion object {
private val SYNTAX_MAP: Map<IElementType, SemanticEditorPosition.SyntaxElement> = hashMapOf( private val SYNTAX_MAP: Map<IElementType, SemanticEditorPosition.SyntaxElement> = hashMapOf(
KtTokens.WHITE_SPACE to Whitespace, 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.EOL_COMMENT to LineComment,
KtTokens.BLOCK_COMMENT to BlockComment, KtTokens.BLOCK_COMMENT to BlockComment,
KtTokens.DOC_COMMENT to KDoc, KtTokens.DOC_COMMENT to KDoc,
KtTokens.ARROW to Arrow, KtTokens.ARROW to Arrow,
KtTokens.LONG_TEMPLATE_ENTRY_START to TemplateEntryOpen,
KtTokens.LONG_TEMPLATE_ENTRY_END to TemplateEntryClose,
KtTokens.LBRACE to BlockOpeningBrace, KtTokens.LBRACE to BlockOpeningBrace,
KtTokens.RBRACE to BlockClosingBrace, KtTokens.RBRACE to BlockClosingBrace,
KtTokens.LPAR to LeftParenthesis, KtTokens.LPAR to LeftParenthesis,
KtTokens.RPAR to RightParenthesis, 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.IF_KEYWORD to IfKeyword,
KtTokens.ELSE_KEYWORD to ElseKeyword, KtTokens.ELSE_KEYWORD to ElseKeyword,
KtTokens.WHEN_KEYWORD to WhenKeyword, KtTokens.WHEN_KEYWORD to WhenKeyword,
@@ -569,12 +178,9 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
KtTokens.WHILE_KEYWORD to WhileKeyword, KtTokens.WHILE_KEYWORD to WhileKeyword,
KtTokens.DO_KEYWORD to DoKeyword, KtTokens.DO_KEYWORD to DoKeyword,
KtTokens.FOR_KEYWORD to ForKeyword, KtTokens.FOR_KEYWORD to ForKeyword,
KtTokens.REGULAR_STRING_PART to RegularStringPart, KtTokens.REGULAR_STRING_PART to RegularStringPart,
KtTokens.LBRACKET to ArrayOpeningBracket,
KtTokens.RBRACKET to ArrayClosingBracket,
KtTokens.IDENTIFIER to Identifier, KtTokens.IDENTIFIER to Identifier,
KtTokens.LT to OpenTypeBrace,
KtTokens.GT to CloseTypeBrace,
KtTokens.FUN_KEYWORD to FunctionKeyword, KtTokens.FUN_KEYWORD to FunctionKeyword,
KtTokens.DOT to Dot, KtTokens.DOT to Dot,
KtTokens.QUEST to Quest, KtTokens.QUEST to Quest,
@@ -602,6 +208,405 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
LineComment, LineComment,
BlockComment, 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
}
} }
} }