FIR IDE: introduce KtQuickFixService
This commit is contained in:
+30
-10
@@ -9,10 +9,15 @@ import com.intellij.codeHighlighting.TextEditorHighlightingPass
|
|||||||
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory
|
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory
|
||||||
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactoryRegistrar
|
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactoryRegistrar
|
||||||
import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar
|
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.HighlightInfo
|
||||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
|
|
||||||
import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil
|
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.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.editor.Document
|
import com.intellij.openapi.editor.Document
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.progress.ProgressIndicator
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
@@ -20,27 +25,41 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.analyze
|
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.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.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
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
class KotlinHighLevelDiagnosticHighlightingPass(
|
class KotlinHighLevelDiagnosticHighlightingPass(
|
||||||
private val ktFile: KtFile,
|
private val ktFile: KtFile,
|
||||||
document: Document,
|
document: Document,
|
||||||
) : TextEditorHighlightingPass(ktFile.project, document) {
|
) : TextEditorHighlightingPass(ktFile.project, document) {
|
||||||
private val diagnosticInfos = mutableListOf<HighlightInfo>()
|
|
||||||
|
|
||||||
override fun doCollectInformation(progress: ProgressIndicator) = analyze(ktFile) {
|
@Suppress("UnstableApiUsage")
|
||||||
ktFile.collectDiagnosticsForFile().forEach { diagnostic ->
|
val annotationHolder = AnnotationHolderImpl(AnnotationSession(ktFile))
|
||||||
diagnostic.textRanges.forEach { range ->
|
|
||||||
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR/*TODO*/)
|
override fun doCollectInformation(progress: ProgressIndicator) {
|
||||||
.descriptionAndTooltip(diagnostic.getMessageToRender())
|
analyze(ktFile) {
|
||||||
.range(range)
|
ktFile.collectDiagnosticsForFile().forEach { diagnostic ->
|
||||||
.create()
|
addDiagnostic(diagnostic)
|
||||||
?.let(diagnosticInfos::add)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 =
|
private fun KtDiagnostic.getMessageToRender(): String =
|
||||||
if (isInternalOrUnitTestMode())
|
if (isInternalOrUnitTestMode())
|
||||||
getDefaultMessageWithFactoryName()
|
getDefaultMessageWithFactoryName()
|
||||||
@@ -53,6 +72,7 @@ class KotlinHighLevelDiagnosticHighlightingPass(
|
|||||||
|
|
||||||
|
|
||||||
override fun doApplyInformationToEditor() {
|
override fun doApplyInformationToEditor() {
|
||||||
|
val diagnosticInfos = annotationHolder.map(HighlightInfo::fromAnnotation)
|
||||||
UpdateHighlightersUtil.setHighlightersToEditor(
|
UpdateHighlightersUtil.setHighlightersToEditor(
|
||||||
myProject, myDocument!!, /*startOffset=*/0, ktFile.textLength, diagnosticInfos, colorsScheme, id
|
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>
|
||||||
@@ -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/ide-frontend-independent.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||||
|
|
||||||
|
<xi:include href="extensions.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||||
|
|
||||||
|
|
||||||
<!-- CIDR-PLUGIN-EXCLUDE-START -->
|
<!-- CIDR-PLUGIN-EXCLUDE-START -->
|
||||||
<!-- <xi:include href="jvm-common.xml" xpointer="xpointer(/idea-plugin/*)"/>-->
|
<!-- <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"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.util.FirPluginOracleService"
|
||||||
serviceImplementation="org.jetbrains.kotlin.idea.util.FirPluginOracleServiceFirImpl"/>
|
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"/>
|
<readWriteAccessDetector implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector" id="kotlin"/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user