diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index db77cd669f7..f829bb74873 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -882,10 +882,6 @@ fun main(args: Array) { model("codeInsight/codeVision") } - testClass { - model("codeInsight/hints/lambda") - } - testClass { model("codeInsight/hints/suspending") } diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinAbstractHintsProvider.kt b/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinAbstractHintsProvider.kt index 6340bf1c65d..5a104f7a9f2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinAbstractHintsProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinAbstractHintsProvider.kt @@ -23,6 +23,20 @@ abstract class KotlinAbstractHintsProvider : InlayHintsProvider { abstract fun isElementSupported(resolved: HintType?, settings: T): Boolean + /** + * By default [PresentationAndSettings] go directly to the [sink] and later are handled by the outer infrastructure. + * The thing is that for lambdas this approach doesn't work: user shouldn't be able to place the caret behind a hint. + * Therefore [KotlinLambdasHintsProvider] provides its own "crutch" implementation. + */ + protected open fun handlePresentations( + presentations: List, + editor: Editor, + sink: InlayHintsSink + ) { + presentations.forEach { p -> + sink.addInlineElement(p.offset, p.relatesToPrecedingText, p.presentation) + } + } override fun getCollectorFor(file: PsiFile, editor: Editor, settings: T, sink: InlayHintsSink): InlayHintsCollector? { return object : FactoryInlayHintsCollector(editor) { @@ -30,13 +44,13 @@ abstract class KotlinAbstractHintsProvider : InlayHintsProvider { val resolved = HintType.resolve(element) ?: return true if (!isElementSupported(resolved, settings)) return true - resolved.provideHints(element) - .mapNotNull { info -> convert(info, editor.project) } - .forEach { triple -> sink.addInlineElement(triple.first, triple.second, triple.third) } + val presentations = resolved.provideHints(element).mapNotNull { info -> convert(info, editor.project) } + if (presentations.isNotEmpty()) + handlePresentations(presentations, editor, sink) return true } - fun convert(inlayInfo: InlayInfo, project: Project?): Triple? { + fun convert(inlayInfo: InlayInfo, project: Project?): PresentationAndSettings? { val inlayText = getInlayPresentation(inlayInfo.text) val presentation = factory.roundWithBackground(factory.smallText(inlayText)) @@ -51,7 +65,7 @@ abstract class KotlinAbstractHintsProvider : InlayHintsProvider { }, left = 1 ) - return Triple(inlayInfo.offset, inlayInfo.relatesToPrecedingText, finalPresentation) + return PresentationAndSettings(finalPresentation, inlayInfo.offset, inlayInfo.relatesToPrecedingText) } fun getInlayPresentation(inlayText: String): String = @@ -62,4 +76,6 @@ abstract class KotlinAbstractHintsProvider : InlayHintsProvider { } } } + + data class PresentationAndSettings(val presentation: InlayPresentation, val offset: Int, val relatesToPrecedingText: Boolean) } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinLambdasHintsProvider.kt b/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinLambdasHintsProvider.kt index a771aef2250..0ab0e0f5398 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinLambdasHintsProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinLambdasHintsProvider.kt @@ -7,10 +7,31 @@ package org.jetbrains.kotlin.idea.codeInsight.hints import com.intellij.codeInsight.hints.ChangeListener import com.intellij.codeInsight.hints.ImmediateConfigurable +import com.intellij.codeInsight.hints.InlayHintsSink +import com.intellij.codeInsight.hints.presentation.PresentationRenderer +import com.intellij.openapi.application.TransactionGuard +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.editor.Editor import com.intellij.ui.layout.panel +import com.sun.glass.ui.Application import org.jetbrains.kotlin.idea.KotlinBundle import javax.swing.JComponent +/** + * Please note that the executable test class is currently not generated. The thing is that [KotlinLambdasHintsProvider] utilizes a hack + * preventing it from being testable (see [handlePresentations]). Nevertheless [AbstractKotlinLambdasHintsProvider] and corresponding + * [testData][idea/testData/codeInsight/hints/lambda] exist. + + * To run the tests in "imaginary" environment (might still be valuable): + * 1. Comment out [handlePresentations] + * 2. Add the following code snippet next to the similar ones at [GenerateTests.kt]: + * ``` + * testClass { + * model("codeInsight/hints/lambda") + * } + * ``` + * 3. Run "Generate All Tests". You're expected to get `KotlinLambdasHintsProviderGenerated` class. + */ @Suppress("UnstableApiUsage") class KotlinLambdasHintsProvider : KotlinAbstractHintsProvider() { @@ -29,6 +50,16 @@ class KotlinLambdasHintsProvider : KotlinAbstractHintsProvider, editor: Editor, sink: InlayHintsSink) { + // sink should remain empty for the outer infrastructure - we place hints ourselves + invokeLater { + presentations.forEach { p -> + editor.inlayModel.getAfterLineEndElementsInRange(p.offset, p.offset).singleOrNull()?.dispose() + editor.inlayModel.addAfterLineEndElement(p.offset, p.relatesToPrecedingText, PresentationRenderer(p.presentation)) + } + } + } + override fun createConfigurable(settings: Settings): ImmediateConfigurable { return object : ImmediateConfigurable { override fun createComponent(listener: ChangeListener): JComponent = panel {} diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinLambdasHintsProviderGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinLambdasHintsProviderGenerated.java deleted file mode 100644 index 02765677445..00000000000 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinLambdasHintsProviderGenerated.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.idea.codeInsight.hints; - -import com.intellij.testFramework.TestDataPath; -import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; -import org.jetbrains.kotlin.test.KotlinTestUtils; -import org.jetbrains.kotlin.test.TestMetadata; -import org.junit.runner.RunWith; - -import java.io.File; -import java.util.regex.Pattern; - -/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ -@SuppressWarnings("all") -@TestMetadata("idea/testData/codeInsight/hints/lambda") -@TestDataPath("$PROJECT_ROOT") -@RunWith(JUnit3RunnerWithInners.class) -public class KotlinLambdasHintsProviderGenerated extends AbstractKotlinLambdasHintsProvider { - private void runTest(String testDataFilePath) throws Exception { - KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); - } - - public void testAllFilesPresentInLambda() throws Exception { - KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/codeInsight/hints/lambda"), Pattern.compile("^(.+)\\.kt$"), null, true); - } - - @TestMetadata("AnnotatedStatement.kt") - public void testAnnotatedStatement() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/AnnotatedStatement.kt"); - } - - @TestMetadata("DisabledHints.kt") - public void testDisabledHints() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/DisabledHints.kt"); - } - - @TestMetadata("Elvis.kt") - public void testElvis() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/Elvis.kt"); - } - - @TestMetadata("If.kt") - public void testIf() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/If.kt"); - } - - @TestMetadata("ImplicitIt.kt") - public void testImplicitIt() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/ImplicitIt.kt"); - } - - @TestMetadata("ImplicitSingleLine.kt") - public void testImplicitSingleLine() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/ImplicitSingleLine.kt"); - } - - @TestMetadata("ImplicitThis.kt") - public void testImplicitThis() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/ImplicitThis.kt"); - } - - @TestMetadata("Label.kt") - public void testLabel() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/Label.kt"); - } - - @TestMetadata("LabeledStatement.kt") - public void testLabeledStatement() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/LabeledStatement.kt"); - } - - @TestMetadata("Nested.kt") - public void testNested() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/Nested.kt"); - } - - @TestMetadata("NoHintForSingleExpression.kt") - public void testNoHintForSingleExpression() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/NoHintForSingleExpression.kt"); - } - - @TestMetadata("OneLineIf.kt") - public void testOneLineIf() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/OneLineIf.kt"); - } - - @TestMetadata("PostfixPrefixExpr.kt") - public void testPostfixPrefixExpr() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/PostfixPrefixExpr.kt"); - } - - @TestMetadata("Qualified.kt") - public void testQualified() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/Qualified.kt"); - } - - @TestMetadata("ReturnFunType.kt") - public void testReturnFunType() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/ReturnFunType.kt"); - } - - @TestMetadata("SimpleCase.kt") - public void testSimpleCase() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/SimpleCase.kt"); - } - - @TestMetadata("When.kt") - public void testWhen() throws Exception { - runTest("idea/testData/codeInsight/hints/lambda/When.kt"); - } -}