FIR IDE: introduce KtQuickFixService

This commit is contained in:
Ilya Kirillov
2021-02-02 21:48:15 +01:00
parent 05fb88d2d9
commit e9a5749cf4
5 changed files with 141 additions and 11 deletions
@@ -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<HighlightInfo>()
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<KtQuickFixService>().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<IntentionAction>) =
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
)
@@ -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<IntentionAction> =
list.getQuickFixesFor(diagnostic)
}
abstract class KtQuickFixRegistrar {
protected abstract val list: KtQuickFixesList
companion object {
private val EP_NAME: ExtensionPointName<KtQuickFixRegistrar> =
ExtensionPointName.create("org.jetbrains.kotlin.ktQuickFixRegistrar")
fun allQuickFixesList() = EP_NAME.extensionList.map { it.list }
}
}
@@ -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<KClass<out KtDiagnosticWithPsi>, MutableList<QuickFixFactory>>()
inline fun <reified D : KtDiagnosticWithPsi> 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<KClass<out KtDiagnosticWithPsi>, List<QuickFixFactory>>) {
fun getQuickFixesFor(diagnostic: KtDiagnosticWithPsi): List<IntentionAction> {
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>): KtQuickFixesList {
val allQuickFixes = registrars.map { it.quickFixes }.merge()
return KtQuickFixesList(allQuickFixes)
}
fun createCombined(vararg registrars: KtQuickFixesList): KtQuickFixesList {
return createCombined(registrars.toList())
}
}
}
private fun <K, V> List<Map<K, List<V>>>.merge(): Map<K, List<V>> {
return flatMap { it.entries }
.groupingBy { it.key }
.aggregate<Map.Entry<K, List<V>>, K, MutableList<V>> { _, accumulator, element, _ ->
val list = accumulator ?: mutableListOf()
list.addAll(element.value)
list
}
}
@@ -0,0 +1,13 @@
<idea-plugin>
<extensionPoints>
<extensionPoint qualifiedName="org.jetbrains.kotlin.ktQuickFixRegistrar"
interface="org.jetbrains.kotlin.idea.quickfix.KtQuickFixRegistrar"
dynamic="true"/>
</extensionPoints>
<extensions defaultExtensionNs="org.jetbrains.kotlin">
<ktQuickFixRegistrar implementation="org.jetbrains.kotlin.idea.quickfix.MainKtQuickFixRegistrar"/>
</extensions>
</idea-plugin>
+3 -1
View File
@@ -40,6 +40,8 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu
<xi:include href="extensions/ide-frontend-independent.xml" xpointer="xpointer(/idea-plugin/*)"/>
<xi:include href="extensions.xml" xpointer="xpointer(/idea-plugin/*)"/>
<!-- CIDR-PLUGIN-EXCLUDE-START -->
<!-- <xi:include href="jvm-common.xml" xpointer="xpointer(/idea-plugin/*)"/>-->
@@ -267,7 +269,7 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu
<projectService serviceInterface="org.jetbrains.kotlin.idea.util.FirPluginOracleService"
serviceImplementation="org.jetbrains.kotlin.idea.util.FirPluginOracleServiceFirImpl"/>
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.quickfix.KtQuickFixService"/>
<readWriteAccessDetector implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector" id="kotlin"/>