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 {
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
}
}
}