Kotlin Facet: Implement automatic facet configuration

This commit is contained in:
Alexey Sedunov
2016-11-07 14:51:52 +03:00
parent 4613eed406
commit dc33436c64
3 changed files with 255 additions and 0 deletions

View File

@@ -40,6 +40,9 @@
<component>
<implementation-class>org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.kotlin.idea.facet.FacetConfigurator</implementation-class>
</component>
</project-components>
<application-components>
@@ -743,6 +746,8 @@
<facetType implementation="org.jetbrains.kotlin.idea.facet.KotlinFacetType"/>
<framework.detector implementation="org.jetbrains.kotlin.idea.facet.KotlinFrameworkDetector"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention</className>
<category>Kotlin</category>

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.facet
import com.intellij.codeHighlighting.TextEditorHighlightingPass
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory
import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar
import com.intellij.framework.detection.DetectedFrameworkDescription
import com.intellij.framework.detection.DetectionExcludesConfiguration
import com.intellij.framework.detection.impl.*
import com.intellij.framework.detection.impl.exclude.DetectionExcludesConfigurationImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.AbstractProjectComponent
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.PlatformModifiableModelsProvider
import com.intellij.openapi.roots.ui.configuration.DefaultModulesProvider
import com.intellij.openapi.startup.StartupManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.indexing.FileBasedIndex
import com.intellij.util.ui.update.MergingUpdateQueue
import com.intellij.util.ui.update.Update
import java.util.*
// Based on com.intellij.framework.detection.impl.FrameworkDetectionManager
class FacetConfigurator(
project: Project,
highlightingPassRegistrar: TextEditorHighlightingPassRegistrar
) : AbstractProjectComponent(project), FrameworkDetectionIndexListener, TextEditorHighlightingPassFactory {
private val myDetectionUpdate = object : Update("detection") {
override fun run() {
doRunDetection()
}
}
private @Volatile var shouldDetect = false
private var myDetectionQueue: MergingUpdateQueue? = null
private var myDetectedFrameworksData: DetectedFrameworksData? = null
init {
highlightingPassRegistrar.registerTextEditorHighlightingPass(this, TextEditorHighlightingPassRegistrar.Anchor.LAST, -1, false, false)
}
override fun initComponent() {
if (!myProject.isDefault && !ApplicationManager.getApplication().isUnitTestMode) {
doInitialize()
}
}
fun doInitialize() {
myDetectionQueue = MergingUpdateQueue("FrameworkDetectionQueue", 500, true, null, myProject)
if (ApplicationManager.getApplication().isUnitTestMode) {
myDetectionQueue!!.isPassThrough = false
myDetectionQueue!!.hideNotify()
}
myDetectedFrameworksData = DetectedFrameworksData(myProject)
FrameworkDetectionIndex.getInstance().addListener(this, myProject)
myProject.messageBus.connect().subscribe(
DumbService.DUMB_MODE,
object : DumbService.DumbModeListener {
override fun enteredDumbMode() = myDetectionQueue!!.suspend()
override fun exitDumbMode() = myDetectionQueue!!.resume()
}
)
}
override fun projectOpened() {
StartupManager.getInstance(myProject).registerPostStartupActivity {
shouldDetect = true
queueDetection()
}
}
override fun disposeComponent() {
doDispose()
}
fun doDispose() {
if (myDetectedFrameworksData != null) {
myDetectedFrameworksData!!.saveDetected()
myDetectedFrameworksData = null
}
}
override fun fileUpdated(file: VirtualFile, detectorId: Int) {
if (detectorId != KotlinFrameworkDetector.detectorIndex) return
shouldDetect = true
queueDetection()
}
private fun queueDetection() {
if (myDetectionQueue != null) {
myDetectionQueue!!.queue(myDetectionUpdate)
}
}
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
val detectors = FrameworkDetectorRegistry.getInstance().getDetectorIds(file.fileType)
if (!detectors.isEmpty()) {
return FrameworkDetectionHighlightingPass(editor, detectors)
}
return null
}
private fun doRunDetection() {
if (!shouldDetect) return
shouldDetect = false
val index = FileBasedIndex.getInstance()
val newDescriptions = ArrayList<DetectedFrameworkDescription>()
val oldDescriptions = ArrayList<DetectedFrameworkDescription>()
val excludesConfiguration = DetectionExcludesConfiguration.getInstance(myProject)
val id = KotlinFrameworkDetector.detectorIndex
val frameworks = runDetector(id, index, excludesConfiguration, true)
oldDescriptions.addAll(frameworks)
val updated = myDetectedFrameworksData!!.updateFrameworksList(id, frameworks)
newDescriptions.addAll(updated)
oldDescriptions.removeAll(updated)
if (FrameworkDetectionUtil.removeDisabled(newDescriptions, oldDescriptions).isEmpty()) return
val framework = getValidDetectedFramework() ?: return
FrameworkDetectionUtil.setupFrameworks(listOf(framework), PlatformModifiableModelsProvider(), DefaultModulesProvider(myProject))
myDetectedFrameworksData!!.putExistentFrameworkFiles(id, framework.relatedFiles)
}
private fun runDetector(detectorId: Int?,
index: FileBasedIndex,
excludesConfiguration: DetectionExcludesConfiguration,
processNewFilesOnly: Boolean): List<DetectedFrameworkDescription> {
val acceptedFiles = index.getContainingFiles(FrameworkDetectionIndex.NAME, detectorId!!, GlobalSearchScope.projectScope(myProject))
val filesToProcess: Collection<VirtualFile>
if (processNewFilesOnly) {
filesToProcess = myDetectedFrameworksData!!.retainNewFiles(detectorId, acceptedFiles)
}
else {
filesToProcess = ArrayList(acceptedFiles)
}
val detector = FrameworkDetectorRegistry.getInstance().getDetectorById(detectorId) as? KotlinFrameworkDetector ?: return emptyList()
(excludesConfiguration as DetectionExcludesConfigurationImpl).removeExcluded(filesToProcess, detector.frameworkType)
val frameworks: List<DetectedFrameworkDescription>
if (!filesToProcess.isEmpty()) {
frameworks = detector.internalDetect(filesToProcess, FrameworkDetectionContextImpl(myProject))
}
else {
frameworks = arrayListOf<DetectedFrameworkDescription>()
}
return frameworks
}
private fun getValidDetectedFramework(): DetectedFrameworkDescription? {
val id = myDetectedFrameworksData!!.detectorsForDetectedFrameworks.singleOrNull() ?: return null
val index = FileBasedIndex.getInstance()
val excludesConfiguration = DetectionExcludesConfiguration.getInstance(myProject)
val frameworks = runDetector(id, index, excludesConfiguration, false)
return FrameworkDetectionUtil.removeDisabled(frameworks).singleOrNull()
}
private fun ensureIndexIsUpToDate(detectors: Collection<Int>) {
for (detectorId in detectors) {
FileBasedIndex.getInstance().getValues(FrameworkDetectionIndex.NAME, detectorId, GlobalSearchScope.projectScope(myProject))
}
}
private inner class FrameworkDetectionHighlightingPass(
editor: Editor,
private val myDetectors: Collection<Int>
) : TextEditorHighlightingPass(this@FacetConfigurator.myProject, editor.document, false) {
override fun doCollectInformation(progress: ProgressIndicator) {
ensureIndexIsUpToDate(myDetectors)
}
override fun doApplyInformationToEditor() {
}
}
companion object {
fun getInstance(project: Project) = project.getComponent(FacetConfigurator::class.java)!!
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.facet
import com.intellij.framework.detection.DetectedFrameworkDescription
import com.intellij.framework.detection.FacetBasedFrameworkDetector
import com.intellij.framework.detection.FileContentPattern
import com.intellij.framework.detection.FrameworkDetectionContext
import com.intellij.framework.detection.impl.FrameworkDetectorRegistry
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.KotlinFileType
class KotlinFrameworkDetector : FacetBasedFrameworkDetector<KotlinFacet, KotlinFacetConfiguration>(DETECTOR_ID) {
companion object {
val DETECTOR_ID = "kotlin"
val detectorIndex by lazy { FrameworkDetectorRegistry.getInstance().getDetectorId(KotlinFrameworkDetector()) }
}
override fun getFacetType() = KotlinFacetType.INSTANCE
override fun createSuitableFilePattern() = FileContentPattern.fileContent()
override fun getFileType() = KotlinFileType.INSTANCE
// Suppress detection to avoid notification via FrameworkDetectionManager (FacetConfigurator is used instead)
override fun detect(
newFiles: Collection<VirtualFile>,
context: FrameworkDetectionContext
) = emptyList<DetectedFrameworkDescription>()
fun internalDetect(
newFiles: Collection<VirtualFile>,
context: FrameworkDetectionContext
) = super.detect(newFiles, context)
}