From 7c99a6fef4aeaa3264b84539b7bae2dfb116c98e Mon Sep 17 00:00:00 2001 From: Dmitry Gridin Date: Fri, 5 Jun 2020 13:23:18 +0700 Subject: [PATCH] LineIndentProvider: support `catch` and `finally` Part of #KT-22211 --- .../KotlinLikeLangLineIndentProvider.kt | 142 ++++++++++++------ ...ormanceTypingIndentationTestGenerated.java | 10 ++ .../indentationOnNewline/Catch.after.kt | 2 - idea/testData/indentationOnNewline/Catch.kt | 2 - .../indentationOnNewline/Catch2.after.kt | 2 - idea/testData/indentationOnNewline/Catch2.kt | 2 - .../indentationOnNewline/Catch3.after.kt | 2 - idea/testData/indentationOnNewline/Catch3.kt | 2 - .../indentationOnNewline/Catch4.after.kt | 20 +++ idea/testData/indentationOnNewline/Catch4.kt | 19 +++ .../indentationOnNewline/Finally.after.kt | 2 - idea/testData/indentationOnNewline/Finally.kt | 2 - .../indentationOnNewline/Finally2.after.kt | 2 - .../testData/indentationOnNewline/Finally2.kt | 2 - .../indentationOnNewline/Finally3.after.kt | 2 - .../testData/indentationOnNewline/Finally3.kt | 2 - .../indentationOnNewline/Finally4.after.kt | 4 + .../testData/indentationOnNewline/Finally4.kt | 3 + .../TypingIndentationTestBaseGenerated.java | 10 ++ 19 files changed, 165 insertions(+), 67 deletions(-) create mode 100644 idea/testData/indentationOnNewline/Catch4.after.kt create mode 100644 idea/testData/indentationOnNewline/Catch4.kt create mode 100644 idea/testData/indentationOnNewline/Finally4.after.kt create mode 100644 idea/testData/indentationOnNewline/Finally4.kt diff --git a/idea/line-indent-provider/src/org/jetbrains/kotlin/idea/formatter/lineIndent/KotlinLikeLangLineIndentProvider.kt b/idea/line-indent-provider/src/org/jetbrains/kotlin/idea/formatter/lineIndent/KotlinLikeLangLineIndentProvider.kt index 14825e26412..85d4e324e02 100644 --- a/idea/line-indent-provider/src/org/jetbrains/kotlin/idea/formatter/lineIndent/KotlinLikeLangLineIndentProvider.kt +++ b/idea/line-indent-provider/src/org/jetbrains/kotlin/idea/formatter/lineIndent/KotlinLikeLangLineIndentProvider.kt @@ -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 = hashSetOf( + private val CONTROL_FLOW_KEYWORDS: HashSet = 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 = arrayOf( @@ -201,4 +249,12 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider BlockComment, ) } -} \ No newline at end of file +} + +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() \ No newline at end of file diff --git a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceTypingIndentationTestGenerated.java b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceTypingIndentationTestGenerated.java index 2c651a520b0..2d6caa1a901 100644 --- a/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceTypingIndentationTestGenerated.java +++ b/idea/performanceTests/test/org/jetbrains/kotlin/idea/perf/PerformanceTypingIndentationTestGenerated.java @@ -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"); diff --git a/idea/testData/indentationOnNewline/Catch.after.kt b/idea/testData/indentationOnNewline/Catch.after.kt index 3ab9f8087d8..5f1e5e983e9 100644 --- a/idea/testData/indentationOnNewline/Catch.after.kt +++ b/idea/testData/indentationOnNewline/Catch.after.kt @@ -2,5 +2,3 @@ fun a() = try { // do smth } catch (e: Exception) - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Catch.kt b/idea/testData/indentationOnNewline/Catch.kt index 996457726ea..6c1a1530ba8 100644 --- a/idea/testData/indentationOnNewline/Catch.kt +++ b/idea/testData/indentationOnNewline/Catch.kt @@ -1,5 +1,3 @@ fun a() = try { // do smth } catch (e: Exception) - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Catch2.after.kt b/idea/testData/indentationOnNewline/Catch2.after.kt index d874e971304..080336c460f 100644 --- a/idea/testData/indentationOnNewline/Catch2.after.kt +++ b/idea/testData/indentationOnNewline/Catch2.after.kt @@ -4,5 +4,3 @@ fun a() = try { } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Catch2.kt b/idea/testData/indentationOnNewline/Catch2.kt index b3eafffbf1e..a6aa913a774 100644 --- a/idea/testData/indentationOnNewline/Catch2.kt +++ b/idea/testData/indentationOnNewline/Catch2.kt @@ -3,5 +3,3 @@ fun a() = try { } catch (e: Exception) { } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Catch3.after.kt b/idea/testData/indentationOnNewline/Catch3.after.kt index a8e65cbf1e5..1fd93b5a3ad 100644 --- a/idea/testData/indentationOnNewline/Catch3.after.kt +++ b/idea/testData/indentationOnNewline/Catch3.after.kt @@ -4,5 +4,3 @@ fun a() = try { (e: Exception) { } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Catch3.kt b/idea/testData/indentationOnNewline/Catch3.kt index 1d2addf257a..60108f5261c 100644 --- a/idea/testData/indentationOnNewline/Catch3.kt +++ b/idea/testData/indentationOnNewline/Catch3.kt @@ -3,5 +3,3 @@ fun a() = try { } catch (e: Exception) { } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Catch4.after.kt b/idea/testData/indentationOnNewline/Catch4.after.kt new file mode 100644 index 00000000000..3eb0ad279fd --- /dev/null +++ b/idea/testData/indentationOnNewline/Catch4.after.kt @@ -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) + \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Catch4.kt b/idea/testData/indentationOnNewline/Catch4.kt new file mode 100644 index 00000000000..c8fc5271027 --- /dev/null +++ b/idea/testData/indentationOnNewline/Catch4.kt @@ -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) \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Finally.after.kt b/idea/testData/indentationOnNewline/Finally.after.kt index 6570837ac00..82cac0c0514 100644 --- a/idea/testData/indentationOnNewline/Finally.after.kt +++ b/idea/testData/indentationOnNewline/Finally.after.kt @@ -4,5 +4,3 @@ fun a() = try { { } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Finally.kt b/idea/testData/indentationOnNewline/Finally.kt index 8b64b315f58..1669e584cd3 100644 --- a/idea/testData/indentationOnNewline/Finally.kt +++ b/idea/testData/indentationOnNewline/Finally.kt @@ -3,5 +3,3 @@ fun a() = try { } finally { } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Finally2.after.kt b/idea/testData/indentationOnNewline/Finally2.after.kt index 787f2eab810..ccd2291e2fe 100644 --- a/idea/testData/indentationOnNewline/Finally2.after.kt +++ b/idea/testData/indentationOnNewline/Finally2.after.kt @@ -6,5 +6,3 @@ fun a() { a } } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Finally2.kt b/idea/testData/indentationOnNewline/Finally2.kt index 88bf9c031f6..8a1bb08ca6c 100644 --- a/idea/testData/indentationOnNewline/Finally2.kt +++ b/idea/testData/indentationOnNewline/Finally2.kt @@ -5,5 +5,3 @@ fun a() { a } } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Finally3.after.kt b/idea/testData/indentationOnNewline/Finally3.after.kt index c4531b0a8ec..3bf5842f2c9 100644 --- a/idea/testData/indentationOnNewline/Finally3.after.kt +++ b/idea/testData/indentationOnNewline/Finally3.after.kt @@ -4,5 +4,3 @@ fun a() { } finally } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Finally3.kt b/idea/testData/indentationOnNewline/Finally3.kt index 37b45801323..34b4ea3a9b7 100644 --- a/idea/testData/indentationOnNewline/Finally3.kt +++ b/idea/testData/indentationOnNewline/Finally3.kt @@ -3,5 +3,3 @@ fun a() { dos() } finally } - -// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Finally4.after.kt b/idea/testData/indentationOnNewline/Finally4.after.kt new file mode 100644 index 00000000000..cfbf04da8a2 --- /dev/null +++ b/idea/testData/indentationOnNewline/Finally4.after.kt @@ -0,0 +1,4 @@ +fun a() = try { + // do smth +} finally + \ No newline at end of file diff --git a/idea/testData/indentationOnNewline/Finally4.kt b/idea/testData/indentationOnNewline/Finally4.kt new file mode 100644 index 00000000000..13eddde7148 --- /dev/null +++ b/idea/testData/indentationOnNewline/Finally4.kt @@ -0,0 +1,3 @@ +fun a() = try { + // do smth +} finally \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/formatter/TypingIndentationTestBaseGenerated.java b/idea/tests/org/jetbrains/kotlin/formatter/TypingIndentationTestBaseGenerated.java index efbb74b30d7..eb7eb2e7355 100644 --- a/idea/tests/org/jetbrains/kotlin/formatter/TypingIndentationTestBaseGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/formatter/TypingIndentationTestBaseGenerated.java @@ -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");