LineIndentProvider: support catch and finally

Part of #KT-22211
This commit is contained in:
Dmitry Gridin
2020-06-05 13:23:18 +07:00
parent acc15e5fad
commit 7c99a6fef4
19 changed files with 165 additions and 67 deletions
@@ -67,44 +67,38 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
val after = currentPosition.afterOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET)
when {
before.isAt(TemplateEntryOpen) -> {
val baseLineOffset = before.startOffset
val indent = if (!currentPosition.hasEmptyLineAfter(offset) && after.isAt(TemplateEntryClose))
val indent = if (!currentPosition.hasLineBreaksAfter(offset) && after.isAt(TemplateEntryClose))
Indent.getNoneIndent()
else
Indent.getNormalIndent()
return factory.createIndentCalculator(indent) { baseLineOffset }
return factory.createIndentCalculator(indent, before.startOffset)
}
before.isAtAnyOf(TryKeyword, FinallyKeyword) -> return factory.createIndentCalculator(
before.isAtAnyOf(TryKeyword) || before.isFinallyKeyword() -> return factory.createIndentCalculator(
Indent.getNoneIndent(),
IndentCalculator.LINE_BEFORE,
)
after.isAt(TemplateEntryClose) -> {
val indent = if (currentPosition.hasEmptyLineAfter(offset)) Indent.getNormalIndent() else Indent.getNoneIndent()
after.moveBeforeParentheses(TemplateEntryOpen, TemplateEntryClose)
return factory.createIndentCalculator(indent, after.startOffset)
}
}
before.controlFlowStatementBefore()?.let { controlFlowKeywordPosition ->
return factory.createIndentCalculator(
when {
after.isAt(LeftParenthesis) -> Indent.getContinuationIndent()
after.isAtAnyOf(BlockOpeningBrace, Arrow) || controlFlowKeywordPosition.isWhileInsideDoWhile() -> Indent.getNoneIndent()
else -> Indent.getNormalIndent()
},
IndentCalculator.LINE_BEFORE,
)
}
return before.controlFlowStatementBefore()?.let { controlFlowKeywordPosition ->
val indent = when {
controlFlowKeywordPosition.similarToCatchKeyword() -> if (before.isAt(RightParenthesis)) Indent.getNoneIndent() else Indent.getNormalIndent()
after.isAt(LeftParenthesis) -> Indent.getContinuationIndent()
after.isAtAnyOf(BlockOpeningBrace, Arrow) || controlFlowKeywordPosition.isWhileInsideDoWhile() -> Indent.getNoneIndent()
else -> Indent.getNormalIndent()
}
after.takeIf { it.isAt(TemplateEntryClose) }?.let { templateEntryPosition ->
val indent = if (currentPosition.hasEmptyLineAfter(offset)) Indent.getNormalIndent() else Indent.getNoneIndent()
templateEntryPosition.moveBeforeParentheses(TemplateEntryOpen, TemplateEntryClose)
val baseLineOffset = templateEntryPosition.startOffset
return factory.createIndentCalculator(indent) { baseLineOffset }
factory.createIndentCalculator(indent, IndentCalculator.LINE_BEFORE)
}
return null
}
private fun SemanticEditorPosition.moveBeforeIfThisIsWhiteSpaceOrComment() = moveBeforeOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET)
private fun SemanticEditorPosition.isWhileInsideDoWhile(): Boolean {
if (!isAt(WhileKeyword)) return false
with(copy()) {
@@ -129,28 +123,86 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
}
private fun SemanticEditorPosition.controlFlowStatementBefore(): SemanticEditorPosition? = with(copy()) {
if (isAt(BlockOpeningBrace)) {
moveBefore()
moveBeforeParentheses(LeftParenthesis, RightParenthesis)
moveBeforeIfThisIsWhiteSpaceOrComment()
}
if (isAt(BlockOpeningBrace)) moveBeforeIgnoringWhiteSpaceOrComment()
if (currElement in CONTROL_FLOW_CONSTRUCTIONS) return this
if (!isAt(RightParenthesis)) return null
if (isControlFlowKeyword()) return this
if (!moveBeforeParenthesesIfPossible()) return null
moveBeforeParentheses(LeftParenthesis, RightParenthesis)
moveBeforeIfThisIsWhiteSpaceOrComment()
return takeIf { currElement in CONTROL_FLOW_CONSTRUCTIONS }
return takeIf { isControlFlowKeyword() }
}
enum class KotlinElement : SemanticEditorPosition.SyntaxElement {
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(KtTokens.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(KtTokens.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.moveBeforeBlockIfPossible(): Boolean = moveBeforeParenthesesIfPossible(
leftParenthesis = BlockOpeningBrace,
rightParenthesis = BlockClosingBrace
)
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)
moveBeforeIfThisIsWhiteSpaceOrComment()
return true
}
private fun SemanticEditorPosition.moveBeforeIfThisIsWhiteSpaceOrComment() = moveBeforeOptionalMix(*WHITE_SPACE_OR_COMMENT_BIT_SET)
private fun SemanticEditorPosition.moveBeforeIgnoringWhiteSpaceOrComment() {
moveBefore()
moveBeforeIfThisIsWhiteSpaceOrComment()
}
private enum class KotlinElement : SemanticEditorPosition.SyntaxElement {
TemplateEntryOpen,
TemplateEntryClose,
Arrow,
WhenKeyword,
CatchKeyword,
FinallyKeyword,
WhileKeyword,
RegularStringPart,
KDoc,
@@ -173,8 +225,6 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
KtTokens.ELSE_KEYWORD to ElseKeyword,
KtTokens.WHEN_KEYWORD to WhenKeyword,
KtTokens.TRY_KEYWORD to TryKeyword,
KtTokens.CATCH_KEYWORD to CatchKeyword,
KtTokens.FINALLY_KEYWORD to FinallyKeyword,
KtTokens.WHILE_KEYWORD to WhileKeyword,
KtTokens.DO_KEYWORD to DoKeyword,
KtTokens.FOR_KEYWORD to ForKeyword,
@@ -183,7 +233,7 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
KtTokens.RBRACKET to ArrayClosingBracket,
)
private val CONTROL_FLOW_CONSTRUCTIONS: HashSet<SemanticEditorPosition.SyntaxElement> = hashSetOf(
private val CONTROL_FLOW_KEYWORDS: HashSet<SemanticEditorPosition.SyntaxElement> = hashSetOf(
WhenKeyword,
IfKeyword,
ElseKeyword,
@@ -191,8 +241,6 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
WhileKeyword,
ForKeyword,
TryKeyword,
CatchKeyword,
FinallyKeyword,
)
private val WHITE_SPACE_OR_COMMENT_BIT_SET: Array<SemanticEditorPosition.SyntaxElement> = arrayOf(
@@ -201,4 +249,12 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
BlockComment,
)
}
}
}
private fun JavaLikeLangLineIndentProvider.IndentCalculatorFactory.createIndentCalculator(
indent: Indent?,
baseLineOffset: Int
): IndentCalculator? = createIndentCalculator(indent) { baseLineOffset }
private fun SemanticEditorPosition.textOfCurrentPosition(): String =
if (isAtEnd) "" else chars.subSequence(startOffset, after().startOffset).toString()
@@ -93,6 +93,11 @@ public class PerformanceTypingIndentationTestGenerated extends AbstractPerforman
runTest("idea/testData/indentationOnNewline/Catch3.kt");
}
@TestMetadata("Catch4.kt")
public void testCatch4() throws Exception {
runTest("idea/testData/indentationOnNewline/Catch4.kt");
}
@TestMetadata("ConsecutiveCallsAfterDot.kt")
public void testConsecutiveCallsAfterDot() throws Exception {
runTest("idea/testData/indentationOnNewline/ConsecutiveCallsAfterDot.kt");
@@ -223,6 +228,11 @@ public class PerformanceTypingIndentationTestGenerated extends AbstractPerforman
runTest("idea/testData/indentationOnNewline/Finally3.kt");
}
@TestMetadata("Finally4.kt")
public void testFinally4() throws Exception {
runTest("idea/testData/indentationOnNewline/Finally4.kt");
}
@TestMetadata("For.kt")
public void testFor() throws Exception {
runTest("idea/testData/indentationOnNewline/For.kt");
-2
View File
@@ -2,5 +2,3 @@ fun a() = try {
// do smth
} catch (e: Exception)
<caret>
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -1,5 +1,3 @@
fun a() = try {
// do smth
} catch (e: Exception)<caret>
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -4,5 +4,3 @@ fun a() = try {
<caret>
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -3,5 +3,3 @@ fun a() = try {
} catch (e: Exception) {<caret>
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -4,5 +4,3 @@ fun a() = try {
<caret>(e: Exception) {
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -3,5 +3,3 @@ fun a() = try {
} catch <caret>(e: Exception) {
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
+20
View File
@@ -0,0 +1,20 @@
fun a() = try {
// do smth
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception)
<caret>
+19
View File
@@ -0,0 +1,19 @@
fun a() = try {
// do smth
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception) {
} catch (e: Exception)<caret>
-2
View File
@@ -4,5 +4,3 @@ fun a() = try {
<caret>{
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -3,5 +3,3 @@ fun a() = try {
} finally<caret> {
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -6,5 +6,3 @@ fun a() {
a
}
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -5,5 +5,3 @@ fun a() {
a
}
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -4,5 +4,3 @@ fun a() {
} finally
<caret>
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
-2
View File
@@ -3,5 +3,3 @@ fun a() {
dos()
} finally<caret>
}
// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER
+4
View File
@@ -0,0 +1,4 @@
fun a() = try {
// do smth
} finally
<caret>
+3
View File
@@ -0,0 +1,3 @@
fun a() = try {
// do smth
} finally<caret>
@@ -95,6 +95,11 @@ public class TypingIndentationTestBaseGenerated extends AbstractTypingIndentatio
runTest("idea/testData/indentationOnNewline/Catch3.after.kt");
}
@TestMetadata("Catch4.after.kt")
public void testCatch4() throws Exception {
runTest("idea/testData/indentationOnNewline/Catch4.after.kt");
}
@TestMetadata("ConsecutiveCallsAfterDot.after.kt")
public void testConsecutiveCallsAfterDot() throws Exception {
runTest("idea/testData/indentationOnNewline/ConsecutiveCallsAfterDot.after.kt");
@@ -225,6 +230,11 @@ public class TypingIndentationTestBaseGenerated extends AbstractTypingIndentatio
runTest("idea/testData/indentationOnNewline/Finally3.after.kt");
}
@TestMetadata("Finally4.after.kt")
public void testFinally4() throws Exception {
runTest("idea/testData/indentationOnNewline/Finally4.after.kt");
}
@TestMetadata("For.after.kt")
public void testFor() throws Exception {
runTest("idea/testData/indentationOnNewline/For.after.kt");