From e9a5749cf46615bcf23d280070854e7b3bd0d192 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Tue, 2 Feb 2021 21:48:15 +0100 Subject: [PATCH] FIR IDE: introduce KtQuickFixService --- ...tlinHighLevelDiagnosticHighlightingPass.kt | 40 ++++++++--- .../kotlin/idea/quickfix/KtQuickFixService.kt | 28 ++++++++ .../kotlin/idea/quickfix/KtQuickFixesList.kt | 67 +++++++++++++++++++ idea/resources-fir/META-INF/extensions.xml | 13 ++++ idea/resources-fir/META-INF/plugin.xml | 4 +- 5 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixService.kt create mode 100644 idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixesList.kt create mode 100644 idea/resources-fir/META-INF/extensions.xml diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/KotlinHighLevelDiagnosticHighlightingPass.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/KotlinHighLevelDiagnosticHighlightingPass.kt index 4e4eb5b20e8..f4f5ce128a9 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/KotlinHighLevelDiagnosticHighlightingPass.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/KotlinHighLevelDiagnosticHighlightingPass.kt @@ -9,10 +9,15 @@ import com.intellij.codeHighlighting.TextEditorHighlightingPass import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory import com.intellij.codeHighlighting.TextEditorHighlightingPassFactoryRegistrar import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar +import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl import com.intellij.codeInsight.daemon.impl.HighlightInfo -import com.intellij.codeInsight.daemon.impl.HighlightInfoType import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.lang.annotation.AnnotationBuilder +import com.intellij.lang.annotation.AnnotationSession +import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.progress.ProgressIndicator @@ -20,27 +25,41 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.frontend.api.analyze import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnostic +import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi import org.jetbrains.kotlin.idea.frontend.api.diagnostics.getDefaultMessageWithFactoryName +import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic +import org.jetbrains.kotlin.idea.quickfix.KtQuickFixService import org.jetbrains.kotlin.psi.KtFile class KotlinHighLevelDiagnosticHighlightingPass( private val ktFile: KtFile, document: Document, ) : TextEditorHighlightingPass(ktFile.project, document) { - private val diagnosticInfos = mutableListOf() - override fun doCollectInformation(progress: ProgressIndicator) = analyze(ktFile) { - ktFile.collectDiagnosticsForFile().forEach { diagnostic -> - diagnostic.textRanges.forEach { range -> - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR/*TODO*/) - .descriptionAndTooltip(diagnostic.getMessageToRender()) - .range(range) - .create() - ?.let(diagnosticInfos::add) + @Suppress("UnstableApiUsage") + val annotationHolder = AnnotationHolderImpl(AnnotationSession(ktFile)) + + override fun doCollectInformation(progress: ProgressIndicator) { + analyze(ktFile) { + ktFile.collectDiagnosticsForFile().forEach { diagnostic -> + addDiagnostic(diagnostic) } } } + private fun addDiagnostic(diagnostic: KtDiagnosticWithPsi) { + val fixes = service().getQuickFixesFor(diagnostic as KtFirDiagnostic) + annotationHolder.runAnnotatorWithContext(diagnostic.psi) { element, annotator -> + annotationHolder.newAnnotation(HighlightSeverity.ERROR, diagnostic.getMessageToRender()) + .addFixes(fixes) + .create() + } + } + + + private fun AnnotationBuilder.addFixes(fixes: List) = + fixes.fold(this, AnnotationBuilder::withFix) + private fun KtDiagnostic.getMessageToRender(): String = if (isInternalOrUnitTestMode()) getDefaultMessageWithFactoryName() @@ -53,6 +72,7 @@ class KotlinHighLevelDiagnosticHighlightingPass( override fun doApplyInformationToEditor() { + val diagnosticInfos = annotationHolder.map(HighlightInfo::fromAnnotation) UpdateHighlightersUtil.setHighlightersToEditor( myProject, myDocument!!, /*startOffset=*/0, ktFile.textLength, diagnosticInfos, colorsScheme, id ) diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixService.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixService.kt new file mode 100644 index 00000000000..78eb52664e6 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixService.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2021 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.quickfix + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.extensions.ExtensionPointName +import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic + +class KtQuickFixService { + private val list = KtQuickFixesList.createCombined(KtQuickFixRegistrar.allQuickFixesList()) + + fun getQuickFixesFor(diagnostic: KtFirDiagnostic): List = + list.getQuickFixesFor(diagnostic) +} + +abstract class KtQuickFixRegistrar { + protected abstract val list: KtQuickFixesList + + companion object { + private val EP_NAME: ExtensionPointName = + ExtensionPointName.create("org.jetbrains.kotlin.ktQuickFixRegistrar") + + fun allQuickFixesList() = EP_NAME.extensionList.map { it.list } + } +} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixesList.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixesList.kt new file mode 100644 index 00000000000..1287431e1ab --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/KtQuickFixesList.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2021 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.quickfix + +import com.intellij.codeInsight.intention.IntentionAction +import org.jetbrains.kotlin.idea.fir.low.level.api.annotations.PrivateForInline +import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnosticWithPsi +import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic +import kotlin.reflect.KClass + +@RequiresOptIn +annotation class ForKtQuickFixesListBuilder() + + +class KtQuickFixesListBuilder private constructor() { + val quickFixes = mutableMapOf, MutableList>() + + inline fun register(quickFixFactory: QuickFixFactory) { + quickFixes.getOrPut(D::class) { mutableListOf() }.add(quickFixFactory) + } + + @OptIn(ForKtQuickFixesListBuilder::class) + private fun build() = KtQuickFixesList(quickFixes) + + companion object { + fun register(init: KtQuickFixesListBuilder.() -> Unit) = KtQuickFixesListBuilder().apply(init).build() + } +} + +class KtQuickFixesList @ForKtQuickFixesListBuilder constructor(private val quickFixes: Map, List>) { + fun getQuickFixesFor(diagnostic: KtDiagnosticWithPsi): List { + val factories = quickFixes[diagnostic.diagnosticClass] ?: return emptyList() + return factories.mapNotNull { it.createQuickFix(diagnostic) } + } + + private fun QuickFixFactory.createQuickFix( + diagnostic: KtDiagnosticWithPsi + ) = when (this) { + is QuickFixesPsiBasedFactory -> createQuickFix(diagnostic.psi) + else -> error("Unsupported QuickFixFactory $this") + } + + companion object { + @OptIn(ForKtQuickFixesListBuilder::class) + fun createCombined(registrars: List): KtQuickFixesList { + val allQuickFixes = registrars.map { it.quickFixes }.merge() + return KtQuickFixesList(allQuickFixes) + } + + fun createCombined(vararg registrars: KtQuickFixesList): KtQuickFixesList { + return createCombined(registrars.toList()) + } + } +} + +private fun List>>.merge(): Map> { + return flatMap { it.entries } + .groupingBy { it.key } + .aggregate>, K, MutableList> { _, accumulator, element, _ -> + val list = accumulator ?: mutableListOf() + list.addAll(element.value) + list + } +} \ No newline at end of file diff --git a/idea/resources-fir/META-INF/extensions.xml b/idea/resources-fir/META-INF/extensions.xml new file mode 100644 index 00000000000..6c246fb2a9f --- /dev/null +++ b/idea/resources-fir/META-INF/extensions.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/idea/resources-fir/META-INF/plugin.xml b/idea/resources-fir/META-INF/plugin.xml index ad428ee635d..1f74267464b 100644 --- a/idea/resources-fir/META-INF/plugin.xml +++ b/idea/resources-fir/META-INF/plugin.xml @@ -40,6 +40,8 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu + + @@ -267,7 +269,7 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu - +