LineIndentProvider: support string templates

Part of #KT-22211
Relates to #KT-38248
Relates to #KT-35244
This commit is contained in:
Dmitry Gridin
2020-05-28 12:30:45 +07:00
parent 3a6b9c8d08
commit 29ceb25f5c
50 changed files with 5343 additions and 8 deletions
@@ -5,14 +5,18 @@
package org.jetbrains.kotlin.idea.formatter.lineIndent
import com.intellij.formatting.Indent
import com.intellij.lang.Language
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.impl.source.codeStyle.SemanticEditorPosition
import com.intellij.psi.impl.source.codeStyle.lineIndent.IndentCalculator
import com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentProvider
import com.intellij.psi.impl.source.codeStyle.lineIndent.JavaLikeLangLineIndentProvider.JavaLikeElement.*
import com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.formatter.lineIndent.KotlinLikeLangLineIndentProvider.KotlinElement.*
import org.jetbrains.kotlin.lexer.KtTokens
abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider() {
abstract fun indentionSettings(project: Project): KotlinIndentationAdjuster
@@ -21,11 +25,46 @@ abstract class KotlinLikeLangLineIndentProvider : JavaLikeLangLineIndentProvider
override fun isSuitableForLanguage(language: Language): Boolean = language.isKindOf(KotlinLanguage.INSTANCE)
override fun getIndent(project: Project, editor: Editor, language: Language?, offset: Int): IndentCalculator? = null
override fun getIndent(project: Project, editor: Editor, language: Language?, offset: Int): IndentCalculator? {
val factory = IndentCalculatorFactory(project, editor)
val currentPosition = getPosition(editor, offset)
currentPosition.beforeOptionalMix(Whitespace, LineComment)
.takeIf { it.isAt(TemplateEntryOpen) }
?.let { templateEntryPosition ->
val baseLineOffset = templateEntryPosition.startOffset
return factory.createIndentCalculator(Indent.getNormalIndent()) { baseLineOffset }
}
currentPosition.afterOptionalMix(Whitespace, LineComment)
.takeIf { it.isAt(TemplateEntryClose) }
?.let { templateEntryPosition ->
val baseLineOffset = templateEntryPosition.beforeParentheses(TemplateEntryOpen, TemplateEntryClose).startOffset
val indent = if (currentPosition.hasEmptyLineAfter(offset)) Indent.getNormalIndent() else Indent.getNoneIndent()
return factory.createIndentCalculator(indent) { baseLineOffset }
}
return null
}
enum class KotlinElement : SemanticEditorPosition.SyntaxElement {
TemplateEntryOpen,
TemplateEntryClose,
Arrow,
WhenKeyword,
}
companion object {
private val SYNTAX_MAP = linkedMapOf<IElementType, SemanticEditorPosition.SyntaxElement>(
private val SYNTAX_MAP: LinkedHashMap<IElementType, SemanticEditorPosition.SyntaxElement> = linkedMapOf(
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.ARROW to Arrow,
KtTokens.LBRACE to BlockOpeningBrace,
KtTokens.RBRACE to BlockClosingBrace,
KtTokens.ELSE_KEYWORD to ElseKeyword,
KtTokens.WHEN_KEYWORD to WhenKeyword,
)
}
}
@@ -238,6 +238,11 @@ public class PerformanceTypingIndentationTestGenerated extends AbstractPerforman
runTest("idea/testData/indentationOnNewline/LargeFile.kt");
}
@TestMetadata("LargeFileWithStringTemplate.kt")
public void testLargeFileWithStringTemplate() throws Exception {
runTest("idea/testData/indentationOnNewline/LargeFileWithStringTemplate.kt");
}
@TestMetadata("ModifierListInUnfinishedDeclaration.kt")
public void testModifierListInUnfinishedDeclaration() throws Exception {
runTest("idea/testData/indentationOnNewline/ModifierListInUnfinishedDeclaration.kt");
@@ -283,6 +288,116 @@ public class PerformanceTypingIndentationTestGenerated extends AbstractPerforman
runTest("idea/testData/indentationOnNewline/SettingAlignMultilineParametersInCalls.kt");
}
@TestMetadata("TemplateEntryClose.kt")
public void testTemplateEntryClose() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose.kt");
}
@TestMetadata("TemplateEntryClose2.kt")
public void testTemplateEntryClose2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose2.kt");
}
@TestMetadata("TemplateEntryClose3.kt")
public void testTemplateEntryClose3() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose3.kt");
}
@TestMetadata("TemplateEntryClose4.kt")
public void testTemplateEntryClose4() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose4.kt");
}
@TestMetadata("TemplateEntryClose5.kt")
public void testTemplateEntryClose5() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose5.kt");
}
@TestMetadata("TemplateEntryClose6.kt")
public void testTemplateEntryClose6() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose6.kt");
}
@TestMetadata("TemplateEntryClose7.kt")
public void testTemplateEntryClose7() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose7.kt");
}
@TestMetadata("TemplateEntryCloseInMultilineString.kt")
public void testTemplateEntryCloseInMultilineString() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryCloseInMultilineString.kt");
}
@TestMetadata("TemplateEntryCloseInMultilineString2.kt")
public void testTemplateEntryCloseInMultilineString2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryCloseInMultilineString2.kt");
}
@TestMetadata("TemplateEntryCloseInMultilineString3.kt")
public void testTemplateEntryCloseInMultilineString3() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryCloseInMultilineString3.kt");
}
@TestMetadata("TemplateEntryOpen.kt")
public void testTemplateEntryOpen() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpen.kt");
}
@TestMetadata("TemplateEntryOpen2.kt")
public void testTemplateEntryOpen2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpen2.kt");
}
@TestMetadata("TemplateEntryOpenInMultilineString.kt")
public void testTemplateEntryOpenInMultilineString() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenInMultilineString.kt");
}
@TestMetadata("TemplateEntryOpenInMultilineString2.kt")
public void testTemplateEntryOpenInMultilineString2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenInMultilineString2.kt");
}
@TestMetadata("TemplateEntryOpenInMultilineString3.kt")
public void testTemplateEntryOpenInMultilineString3() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenInMultilineString3.kt");
}
@TestMetadata("TemplateEntryOpenWithComment.kt")
public void testTemplateEntryOpenWithComment() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithComment.kt");
}
@TestMetadata("TemplateEntryOpenWithComment2.kt")
public void testTemplateEntryOpenWithComment2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithComment2.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent.kt")
public void testTemplateEntryOpenWithoutContent() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent2.kt")
public void testTemplateEntryOpenWithoutContent2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent2.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent3.kt")
public void testTemplateEntryOpenWithoutContent3() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent3.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent4.kt")
public void testTemplateEntryOpenWithoutContent4() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent4.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent5.kt")
public void testTemplateEntryOpenWithoutContent5() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent5.kt");
}
@TestMetadata("While.kt")
public void testWhile() throws Exception {
runTest("idea/testData/indentationOnNewline/While.kt");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,6 @@
fun a() {
val b = 3
val a = "${b
<caret>}"
}
@@ -0,0 +1,5 @@
fun a() {
val b = 3
val a = "${b<caret>}"
}
@@ -0,0 +1,7 @@
fun a() {
val b = 3
val a = "${b
<caret> }"
}
@@ -0,0 +1,6 @@
fun a() {
val b = 3
val a = "${b
<caret> }"
}
@@ -0,0 +1,8 @@
fun a() {
val b = 3
val a = "${
b
<caret>}"
}
@@ -0,0 +1,7 @@
fun a() {
val b = 3
val a = "${
b
<caret>}"
}
@@ -0,0 +1,9 @@
fun a() {
val b = 3
val a = "${
b
<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,8 @@
fun a() {
val b = 3
val a = "${
b<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,9 @@
fun a() {
val b = 3
val a = "${
b
<caret>
// smth
}"
}
@@ -0,0 +1,8 @@
fun a() {
val b = 3
val a = "${
b<caret>
// smth
}"
}
@@ -0,0 +1,10 @@
fun a() {
val b = 3
val a = "" +
"${
b
<caret>
// smth
}"
}
@@ -0,0 +1,9 @@
fun a() {
val b = 3
val a = "" +
"${
b<caret>
// smth
}"
}
@@ -0,0 +1,10 @@
fun a() {
val b = 3
val a = "" +
"${
b
<caret>
// smth
}"
}
@@ -0,0 +1,9 @@
fun a() {
val b = 3
val a = "" +
"${
b<caret>
// smth
}"
}
@@ -0,0 +1,14 @@
fun a() {
val b = 3
val select = """
select
${
a
<caret>
}
from T
"""
}
// KT-35244
// IGNORE_FORMATTER
@@ -0,0 +1,13 @@
fun a() {
val b = 3
val select = """
select
${
a<caret>
}
from T
"""
}
// KT-35244
// IGNORE_FORMATTER
@@ -0,0 +1,11 @@
fun a() {
val b = 3
val select = """
select
${a
<caret>}
from T
"""
}
// IGNORE_FORMATTER
@@ -0,0 +1,10 @@
fun a() {
val b = 3
val select = """
select
${a<caret>}
from T
"""
}
// IGNORE_FORMATTER
@@ -0,0 +1,13 @@
fun a() {
val b = 3
val select = """
select
${
a
<caret>}
from T
"""
}
// IGNORE_FORMATTER
@@ -0,0 +1,12 @@
fun a() {
val b = 3
val select = """
select
${
a
<caret>}
from T
"""
}
// IGNORE_FORMATTER
@@ -0,0 +1,5 @@
fun a() {
val b = 3
val a = "${
<caret>b}"
}
@@ -0,0 +1,4 @@
fun a() {
val b = 3
val a = "${<caret>b}"
}
@@ -0,0 +1,6 @@
fun a() {
val b = 3
val a = "${
<caret>b}"
}
@@ -0,0 +1,5 @@
fun a() {
val b = 3
val a = "${
<caret>b}"
}
@@ -0,0 +1,13 @@
fun a() {
val b = 3
val select = """
select
${
<caret>
}
from T
"""
}
// KT-35244
// IGNORE_FORMATTER
@@ -0,0 +1,11 @@
fun a() {
val b = 3
val select = """
select
${<caret>}
from T
"""
}
// KT-35244
// IGNORE_FORMATTER
@@ -0,0 +1,11 @@
fun a() {
val b = 3
val select = """
select
${
<caret>a}
from T
"""
}
// IGNORE_FORMATTER
@@ -0,0 +1,10 @@
fun a() {
val b = 3
val select = """
select
${<caret>a}
from T
"""
}
// IGNORE_FORMATTER
@@ -0,0 +1,13 @@
fun a() {
val b = 3
val select = """
select
${
<caret>a
}
from T
"""
}
// IGNORE_FORMATTER
@@ -0,0 +1,12 @@
fun a() {
val b = 3
val select = """
select
${
<caret>a
}
from T
"""
}
// IGNORE_FORMATTER
@@ -0,0 +1,10 @@
fun a() {
val b = 3
"" +
"${
// smth
<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,9 @@
fun a() {
val b = 3
"" +
"${
// smth<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,11 @@
fun a() {
val b = 3
"" +
"${
// smth
<caret>
a
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,10 @@
fun a() {
val b = 3
"" +
"${
// smth<caret>
a
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,7 @@
fun a() {
val a = "${
<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,5 @@
fun a() {
val a = "${<caret>}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,8 @@
fun a() {
val a = "${
<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,7 @@
fun a() {
val a = "${
<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,9 @@
fun a() {
val b = 3
"" +
"${
<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,7 @@
fun a() {
val b = 3
"" +
"${<caret>}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,9 @@
fun a() {
val b = 3
"" +
"${
<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,7 @@
fun a() {
val b = 3
"" +
"${<caret>}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,13 @@
fun a() {
val b = 3
"" +
"${
<caret>
}"
}
// IGNORE_FORMATTER
@@ -0,0 +1,12 @@
fun a() {
val b = 3
"" +
"${
<caret>
}"
}
// IGNORE_FORMATTER
@@ -36,6 +36,16 @@ abstract class AbstractTypingIndentationTestBase : KotlinLightPlatformCodeInsigh
"// WITHOUT_CUSTOM_LINE_INDENT_PROVIDER"
) != null
val ignoreFormatter = InTextDirectivesUtils.findStringWithPrefixes(
originalFileText,
"// IGNORE_FORMATTER"
) != null
Assert.assertFalse(
"Only one option of 'WITHOUT_CUSTOM_LINE_INDENT_PROVIDER' and 'IGNORE_FORMATTER' is available at the same time",
withoutCustomLineIndentProvider && ignoreFormatter
)
try {
val configurator = FormatSettingsUtil.createConfigurator(originalFileText, CodeStyle.getSettings(project))
if (!inverted) {
@@ -44,16 +54,27 @@ abstract class AbstractTypingIndentationTestBase : KotlinLightPlatformCodeInsigh
configurator.configureInvertedSettings()
}
doNewlineTest(originFilePath, afterFilePath, withoutCustomLineIndentProvider)
doNewlineTest(
originFilePath, afterFilePath,
withoutCustomLineIndentProvider = withoutCustomLineIndentProvider,
ignoreFormatter = ignoreFormatter
)
} finally {
CodeStyle.getSettings(project).clearCodeStyleSettings()
}
}
private fun doNewlineTest(beforeFilePath: String, afterFilePath: String, withoutCustomLineIndentProvider: Boolean) {
KotlinLineIndentProvider.useFormatter = true
typeAndCheck(beforeFilePath, afterFilePath, "with FormatterBasedLineIndentProvider")
KotlinLineIndentProvider.useFormatter = false
private fun doNewlineTest(
beforeFilePath: String,
afterFilePath: String,
withoutCustomLineIndentProvider: Boolean,
ignoreFormatter: Boolean
) {
if (!ignoreFormatter) {
KotlinLineIndentProvider.useFormatter = true
typeAndCheck(beforeFilePath, afterFilePath, "with FormatterBasedLineIndentProvider")
KotlinLineIndentProvider.useFormatter = false
}
if (!withoutCustomLineIndentProvider) {
typeAndCheck(beforeFilePath, afterFilePath, "with ${customLineIndentProvider.javaClass.simpleName}")
@@ -240,6 +240,11 @@ public class TypingIndentationTestBaseGenerated extends AbstractTypingIndentatio
runTest("idea/testData/indentationOnNewline/LargeFile.after.kt");
}
@TestMetadata("LargeFileWithStringTemplate.after.kt")
public void testLargeFileWithStringTemplate() throws Exception {
runTest("idea/testData/indentationOnNewline/LargeFileWithStringTemplate.after.kt");
}
@TestMetadata("ModifierListInUnfinishedDeclaration.after.kt")
public void testModifierListInUnfinishedDeclaration() throws Exception {
runTest("idea/testData/indentationOnNewline/ModifierListInUnfinishedDeclaration.after.kt");
@@ -285,6 +290,116 @@ public class TypingIndentationTestBaseGenerated extends AbstractTypingIndentatio
runTest("idea/testData/indentationOnNewline/SettingAlignMultilineParametersInCalls.after.kt");
}
@TestMetadata("TemplateEntryClose.after.kt")
public void testTemplateEntryClose() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose.after.kt");
}
@TestMetadata("TemplateEntryClose2.after.kt")
public void testTemplateEntryClose2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose2.after.kt");
}
@TestMetadata("TemplateEntryClose3.after.kt")
public void testTemplateEntryClose3() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose3.after.kt");
}
@TestMetadata("TemplateEntryClose4.after.kt")
public void testTemplateEntryClose4() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose4.after.kt");
}
@TestMetadata("TemplateEntryClose5.after.kt")
public void testTemplateEntryClose5() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose5.after.kt");
}
@TestMetadata("TemplateEntryClose6.after.kt")
public void testTemplateEntryClose6() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose6.after.kt");
}
@TestMetadata("TemplateEntryClose7.after.kt")
public void testTemplateEntryClose7() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryClose7.after.kt");
}
@TestMetadata("TemplateEntryCloseInMultilineString.after.kt")
public void testTemplateEntryCloseInMultilineString() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryCloseInMultilineString.after.kt");
}
@TestMetadata("TemplateEntryCloseInMultilineString2.after.kt")
public void testTemplateEntryCloseInMultilineString2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryCloseInMultilineString2.after.kt");
}
@TestMetadata("TemplateEntryCloseInMultilineString3.after.kt")
public void testTemplateEntryCloseInMultilineString3() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryCloseInMultilineString3.after.kt");
}
@TestMetadata("TemplateEntryOpen.after.kt")
public void testTemplateEntryOpen() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpen.after.kt");
}
@TestMetadata("TemplateEntryOpen2.after.kt")
public void testTemplateEntryOpen2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpen2.after.kt");
}
@TestMetadata("TemplateEntryOpenInMultilineString.after.kt")
public void testTemplateEntryOpenInMultilineString() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenInMultilineString.after.kt");
}
@TestMetadata("TemplateEntryOpenInMultilineString2.after.kt")
public void testTemplateEntryOpenInMultilineString2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenInMultilineString2.after.kt");
}
@TestMetadata("TemplateEntryOpenInMultilineString3.after.kt")
public void testTemplateEntryOpenInMultilineString3() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenInMultilineString3.after.kt");
}
@TestMetadata("TemplateEntryOpenWithComment.after.kt")
public void testTemplateEntryOpenWithComment() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithComment.after.kt");
}
@TestMetadata("TemplateEntryOpenWithComment2.after.kt")
public void testTemplateEntryOpenWithComment2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithComment2.after.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent.after.kt")
public void testTemplateEntryOpenWithoutContent() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent.after.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent2.after.kt")
public void testTemplateEntryOpenWithoutContent2() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent2.after.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent3.after.kt")
public void testTemplateEntryOpenWithoutContent3() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent3.after.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent4.after.kt")
public void testTemplateEntryOpenWithoutContent4() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent4.after.kt");
}
@TestMetadata("TemplateEntryOpenWithoutContent5.after.kt")
public void testTemplateEntryOpenWithoutContent5() throws Exception {
runTest("idea/testData/indentationOnNewline/TemplateEntryOpenWithoutContent5.after.kt");
}
@TestMetadata("While.after.kt")
public void testWhile() throws Exception {
runTest("idea/testData/indentationOnNewline/While.after.kt");