diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index a40708e9be5..07ae7dd56f7 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -40,6 +40,9 @@ org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener + + org.jetbrains.kotlin.idea.facet.FacetConfigurator + @@ -743,6 +746,8 @@ + + org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/FacetConfigurator.kt b/idea/src/org/jetbrains/kotlin/idea/facet/FacetConfigurator.kt new file mode 100644 index 00000000000..ebf39417b87 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/facet/FacetConfigurator.kt @@ -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() + val oldDescriptions = ArrayList() + 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 { + val acceptedFiles = index.getContainingFiles(FrameworkDetectionIndex.NAME, detectorId!!, GlobalSearchScope.projectScope(myProject)) + val filesToProcess: Collection + 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 + if (!filesToProcess.isEmpty()) { + frameworks = detector.internalDetect(filesToProcess, FrameworkDetectionContextImpl(myProject)) + } + else { + frameworks = arrayListOf() + } + 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) { + for (detectorId in detectors) { + FileBasedIndex.getInstance().getValues(FrameworkDetectionIndex.NAME, detectorId, GlobalSearchScope.projectScope(myProject)) + } + } + + private inner class FrameworkDetectionHighlightingPass( + editor: Editor, + private val myDetectors: Collection + ) : 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)!! + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFrameworkDetector.kt b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFrameworkDetector.kt new file mode 100644 index 00000000000..c75e8645c7c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFrameworkDetector.kt @@ -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(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, + context: FrameworkDetectionContext + ) = emptyList() + + fun internalDetect( + newFiles: Collection, + context: FrameworkDetectionContext + ) = super.detect(newFiles, context) +} \ No newline at end of file