KT-32368 Rework Inline hints settings // special case for lambda hints
Lambda hints are placed at the end of a line in such a way that user can't move the caret behind it. Unfortunately, hint provision infrastructure doesn't take this detail into account. As a workaround, KotlinLambdasHintsProvider now returns an empty result (sink) to the outer infrastructure and accesses editor's inlay model itself.
This commit is contained in:
@@ -882,10 +882,6 @@ fun main(args: Array<String>) {
|
||||
model("codeInsight/codeVision")
|
||||
}
|
||||
|
||||
testClass<AbstractKotlinLambdasHintsProvider> {
|
||||
model("codeInsight/hints/lambda")
|
||||
}
|
||||
|
||||
testClass<AbstractKotlinSuspendingCallHintsProviderTest> {
|
||||
model("codeInsight/hints/suspending")
|
||||
}
|
||||
|
||||
+21
-5
@@ -23,6 +23,20 @@ abstract class KotlinAbstractHintsProvider<T : Any> : InlayHintsProvider<T> {
|
||||
|
||||
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<PresentationAndSettings>,
|
||||
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<T : Any> : InlayHintsProvider<T> {
|
||||
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<Int, Boolean, InlayPresentation>? {
|
||||
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<T : Any> : InlayHintsProvider<T> {
|
||||
}, 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<T : Any> : InlayHintsProvider<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class PresentationAndSettings(val presentation: InlayPresentation, val offset: Int, val relatesToPrecedingText: Boolean)
|
||||
}
|
||||
@@ -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<AbstractKotlinLambdasHintsProvider> {
|
||||
* model("codeInsight/hints/lambda")
|
||||
* }
|
||||
* ```
|
||||
* 3. Run "Generate All Tests". You're expected to get `KotlinLambdasHintsProviderGenerated` class.
|
||||
*/
|
||||
@Suppress("UnstableApiUsage")
|
||||
class KotlinLambdasHintsProvider : KotlinAbstractHintsProvider<KotlinLambdasHintsProvider.Settings>() {
|
||||
|
||||
@@ -29,6 +50,16 @@ class KotlinLambdasHintsProvider : KotlinAbstractHintsProvider<KotlinLambdasHint
|
||||
}
|
||||
}
|
||||
|
||||
override fun handlePresentations(presentations: List<PresentationAndSettings>, 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 {}
|
||||
|
||||
Generated
-115
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user