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")
|
model("codeInsight/codeVision")
|
||||||
}
|
}
|
||||||
|
|
||||||
testClass<AbstractKotlinLambdasHintsProvider> {
|
|
||||||
model("codeInsight/hints/lambda")
|
|
||||||
}
|
|
||||||
|
|
||||||
testClass<AbstractKotlinSuspendingCallHintsProviderTest> {
|
testClass<AbstractKotlinSuspendingCallHintsProviderTest> {
|
||||||
model("codeInsight/hints/suspending")
|
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
|
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? {
|
override fun getCollectorFor(file: PsiFile, editor: Editor, settings: T, sink: InlayHintsSink): InlayHintsCollector? {
|
||||||
return object : FactoryInlayHintsCollector(editor) {
|
return object : FactoryInlayHintsCollector(editor) {
|
||||||
@@ -30,13 +44,13 @@ abstract class KotlinAbstractHintsProvider<T : Any> : InlayHintsProvider<T> {
|
|||||||
val resolved = HintType.resolve(element) ?: return true
|
val resolved = HintType.resolve(element) ?: return true
|
||||||
if (!isElementSupported(resolved, settings)) return true
|
if (!isElementSupported(resolved, settings)) return true
|
||||||
|
|
||||||
resolved.provideHints(element)
|
val presentations = resolved.provideHints(element).mapNotNull { info -> convert(info, editor.project) }
|
||||||
.mapNotNull { info -> convert(info, editor.project) }
|
if (presentations.isNotEmpty())
|
||||||
.forEach { triple -> sink.addInlineElement(triple.first, triple.second, triple.third) }
|
handlePresentations(presentations, editor, sink)
|
||||||
return true
|
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 inlayText = getInlayPresentation(inlayInfo.text)
|
||||||
val presentation = factory.roundWithBackground(factory.smallText(inlayText))
|
val presentation = factory.roundWithBackground(factory.smallText(inlayText))
|
||||||
|
|
||||||
@@ -51,7 +65,7 @@ abstract class KotlinAbstractHintsProvider<T : Any> : InlayHintsProvider<T> {
|
|||||||
}, left = 1
|
}, left = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
return Triple(inlayInfo.offset, inlayInfo.relatesToPrecedingText, finalPresentation)
|
return PresentationAndSettings(finalPresentation, inlayInfo.offset, inlayInfo.relatesToPrecedingText)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInlayPresentation(inlayText: String): String =
|
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.ChangeListener
|
||||||
import com.intellij.codeInsight.hints.ImmediateConfigurable
|
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.intellij.ui.layout.panel
|
||||||
|
import com.sun.glass.ui.Application
|
||||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
import javax.swing.JComponent
|
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")
|
@Suppress("UnstableApiUsage")
|
||||||
class KotlinLambdasHintsProvider : KotlinAbstractHintsProvider<KotlinLambdasHintsProvider.Settings>() {
|
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 {
|
override fun createConfigurable(settings: Settings): ImmediateConfigurable {
|
||||||
return object : ImmediateConfigurable {
|
return object : ImmediateConfigurable {
|
||||||
override fun createComponent(listener: ChangeListener): JComponent = panel {}
|
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