From c6c65b1a20c60af59c91a5b954fadf14c9523443 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 5 Oct 2016 16:28:16 +0300 Subject: [PATCH] Kotlin Facet: Initial implementation --- ChangeLog.md | 1 + idea/src/META-INF/plugin.xml | 2 + ...kLibraryValidatorWithDynamicDescription.kt | 72 ++++++++ .../kotlin/idea/facet/KotlinFacet.kt | 34 ++++ .../idea/facet/KotlinFacetConfiguration.kt | 66 +++++++ .../kotlin/idea/facet/KotlinFacetEditorTab.kt | 165 ++++++++++++++++++ .../kotlin/idea/facet/KotlinFacetType.kt | 51 ++++++ .../kotlin/idea/util/DescriptionAware.kt | 21 +++ 8 files changed, 412 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/facet/FrameworkLibraryValidatorWithDynamicDescription.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacet.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetConfiguration.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetEditorTab.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetType.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/util/DescriptionAware.kt diff --git a/ChangeLog.md b/ChangeLog.md index 7176e9c4a42..ca0be947da9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -207,6 +207,7 @@ These artifacts include extensions for the types available in the latter JDKs, s - [`KT-12398`](https://youtrack.jetbrains.com/issue/KT-12398) Call Hierarchy: Show Kotlin usages of Java methods - [`KT-13976`](https://youtrack.jetbrains.com/issue/KT-13976) Search Everywhere: Render function parameter types - [`KT-13977`](https://youtrack.jetbrains.com/issue/KT-13977) Search Everywhere: Render extension type in prefix position +- Implement Kotlin facet #### Intention actions, inspections and quickfixes diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index fe1be9146f2..dae14becd52 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -731,6 +731,8 @@ + + org.jetbrains.kotlin.idea.intentions.FoldInitializerAndIfToElvisIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/FrameworkLibraryValidatorWithDynamicDescription.kt b/idea/src/org/jetbrains/kotlin/idea/facet/FrameworkLibraryValidatorWithDynamicDescription.kt new file mode 100644 index 00000000000..29ee3e95ffa --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/facet/FrameworkLibraryValidatorWithDynamicDescription.kt @@ -0,0 +1,72 @@ +/* + * 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.facet.impl.ui.libraries.LibrariesValidatorContext +import com.intellij.facet.ui.FacetConfigurationQuickFix +import com.intellij.facet.ui.FacetValidatorsManager +import com.intellij.facet.ui.ValidationResult +import com.intellij.facet.ui.libraries.FrameworkLibraryValidator +import com.intellij.ide.IdeBundle +import com.intellij.openapi.roots.ui.configuration.libraries.AddCustomLibraryDialog +import com.intellij.openapi.roots.ui.configuration.libraries.CustomLibraryDescription +import com.intellij.openapi.roots.ui.configuration.libraries.LibraryPresentationManager +import javax.swing.JComponent + +// Based on com.intellij.facet.impl.ui.libraries.FrameworkLibraryValidatorImpl +class FrameworkLibraryValidatorWithDynamicDescription( + private val context: LibrariesValidatorContext, + private val validatorsManager: FacetValidatorsManager, + private val libraryCategoryName: String, + private val getLibraryDescription: () -> CustomLibraryDescription +) : FrameworkLibraryValidator() { + override fun check(): ValidationResult { + val libraryDescription = getLibraryDescription() + val libraryKinds = libraryDescription.suitableLibraryKinds + var found = false + val presentationManager = LibraryPresentationManager.getInstance() + context.rootModel + .orderEntries() + .using(context.modulesProvider) + .recursively() + .librariesOnly() + .forEachLibrary { library -> + if (presentationManager.isLibraryOfKind(library, context.librariesContainer, libraryKinds)) { + found = true + } + !found + } + if (found) return ValidationResult.OK + + return ValidationResult( + IdeBundle.message("label.missed.libraries.text", libraryCategoryName), + LibrariesQuickFix(libraryDescription) + ) + } + + private inner class LibrariesQuickFix( + private val myDescription: CustomLibraryDescription + ) : FacetConfigurationQuickFix(IdeBundle.message("button.fix")) { + override fun run(place: JComponent) { + val dialog = AddCustomLibraryDialog.createDialog(myDescription, context.librariesContainer, + context.module, context.modifiableRootModel, + null) + dialog.show() + validatorsManager.validate() + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacet.kt b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacet.kt new file mode 100644 index 00000000000..f6d2e746997 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacet.kt @@ -0,0 +1,34 @@ +/* + * 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.facet.Facet +import com.intellij.facet.FacetManager.getInstance +import com.intellij.openapi.module.Module + +class KotlinFacet( + module: Module, + name: String, + configuration: KotlinFacetConfiguration +) : Facet(KotlinFacetType.INSTANCE, module, name, configuration, null) { + companion object { + fun get(module: Module): KotlinFacet? { + if (module.isDisposed) return null + return getInstance(module).getFacetByType(KotlinFacetType.TYPE_ID) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetConfiguration.kt b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetConfiguration.kt new file mode 100644 index 00000000000..83a4c134e11 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetConfiguration.kt @@ -0,0 +1,66 @@ +/* + * 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.facet.FacetConfiguration +import com.intellij.facet.ui.FacetEditorContext +import com.intellij.facet.ui.FacetEditorTab +import com.intellij.facet.ui.FacetValidatorsManager +import com.intellij.openapi.components.PersistentStateComponent +import org.jdom.Element +import org.jetbrains.kotlin.idea.util.DescriptionAware + +class KotlinFacetConfiguration : FacetConfiguration, PersistentStateComponent { + enum class LanguageLevel(override val description: String) : DescriptionAware { + KOTLIN_1_0("1.0"), + KOTLIN_1_1("1.1") + } + + enum class TargetPlatform(override val description: String) : DescriptionAware { + JVM_1_6("JVM 1.6"), + JVM_1_8("JVM 1.8"), + JS("JavaScript") + } + + class Settings { + var languageLevel: LanguageLevel? = null + var targetPlatformKind: TargetPlatform? = null + } + + private var settings = Settings() + + @Suppress("OverridingDeprecatedMember") + override fun readExternal(element: Element?) { + + } + + @Suppress("OverridingDeprecatedMember") + override fun writeExternal(element: Element?) { + + } + + override fun loadState(state: Settings) { + this.settings = settings + } + + override fun getState() = settings + + override fun createEditorTabs( + editorContext: FacetEditorContext, + validatorsManager: FacetValidatorsManager + ): Array = arrayOf(KotlinFacetEditorTab(this, editorContext, validatorsManager)) +} diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetEditorTab.kt b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetEditorTab.kt new file mode 100644 index 00000000000..2f688ebfce2 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetEditorTab.kt @@ -0,0 +1,165 @@ +/* + * 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.facet.impl.ui.libraries.DelegatingLibrariesValidatorContext +import com.intellij.facet.ui.FacetEditorContext +import com.intellij.facet.ui.FacetEditorTab +import com.intellij.facet.ui.FacetValidatorsManager +import com.intellij.facet.ui.libraries.FrameworkLibraryValidator +import com.intellij.framework.library.LibraryVersionProperties +import com.intellij.openapi.projectRoots.JavaSdk +import com.intellij.openapi.projectRoots.JavaSdkVersion +import com.intellij.openapi.roots.LibraryOrderEntry +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.roots.ui.configuration.libraries.CustomLibraryDescription +import com.intellij.util.ui.FormBuilder +import org.jetbrains.kotlin.idea.framework.JSLibraryStdDescription +import org.jetbrains.kotlin.idea.framework.JSLibraryStdPresentationProvider +import org.jetbrains.kotlin.idea.framework.JavaRuntimeLibraryDescription +import org.jetbrains.kotlin.idea.framework.getLibraryProperties +import org.jetbrains.kotlin.idea.util.DescriptionAware +import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion +import java.awt.BorderLayout +import java.awt.Component +import javax.swing.* + +class KotlinFacetEditorTab( + private val configuration: KotlinFacetConfiguration, + private val editorContext: FacetEditorContext, + validatorsManager: FacetValidatorsManager +) : FacetEditorTab() { + class DescriptionListCellRenderer : DefaultListCellRenderer() { + override fun getListCellRendererComponent(list: JList<*>?, value: Any?, index: Int, isSelected: Boolean, cellHasFocus: Boolean): Component { + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus).apply { + text = (value as? DescriptionAware)?.description ?: "" + } + } + } + + private val KotlinFacetConfiguration.TargetPlatform.libraryDescription: CustomLibraryDescription + get() { + return when (this) { + KotlinFacetConfiguration.TargetPlatform.JVM_1_6, KotlinFacetConfiguration.TargetPlatform.JVM_1_8 -> + JavaRuntimeLibraryDescription(editorContext.project) + KotlinFacetConfiguration.TargetPlatform.JS -> + JSLibraryStdDescription(editorContext.project) + } + } + + private val languageVersionComboBox = + JComboBox(KotlinFacetConfiguration.LanguageLevel.values()).apply { + setRenderer(DescriptionListCellRenderer()) + } + + private val targetPlatformComboBox = + JComboBox(KotlinFacetConfiguration.TargetPlatform.values()).apply { + setRenderer(DescriptionListCellRenderer()) + } + + private val validator: FrameworkLibraryValidator + + private fun getRuntimeLibraryVersions( + libToProperties: Library.() -> LibraryVersionProperties? + ): List { + return editorContext + .rootModel + .orderEntries + .asSequence() + .filterIsInstance() + .mapNotNull { it.library?.libToProperties()?.versionString } + .toList() + } + + private fun getDefaultTargetPlatform(): KotlinFacetConfiguration.TargetPlatform { + getRuntimeLibraryVersions { getLibraryProperties(JSLibraryStdPresentationProvider.getInstance(), this) }.firstOrNull()?.let { javaLib -> + return KotlinFacetConfiguration.TargetPlatform.JS + } + + val sdk = editorContext.rootModel.sdk + val sdkVersion = (sdk?.sdkType as? JavaSdk)?.getVersion(sdk!!) + return when { + sdkVersion != null && sdkVersion <= JavaSdkVersion.JDK_1_6 -> KotlinFacetConfiguration.TargetPlatform.JVM_1_6 + else -> KotlinFacetConfiguration.TargetPlatform.JVM_1_8 + } + } + + private fun getDefaultLanguageLevel(): KotlinFacetConfiguration.LanguageLevel { + val libVersion = bundledRuntimeVersion() + return when { + libVersion.startsWith("1.0") -> KotlinFacetConfiguration.LanguageLevel.KOTLIN_1_0 + else -> KotlinFacetConfiguration.LanguageLevel.KOTLIN_1_1 + } + } + + init { + validator = FrameworkLibraryValidatorWithDynamicDescription( + DelegatingLibrariesValidatorContext(editorContext), + validatorsManager, + "kotlin" + ) { (targetPlatformComboBox.selectedItem as KotlinFacetConfiguration.TargetPlatform).libraryDescription } + validatorsManager.registerValidator(validator) + + targetPlatformComboBox.addActionListener { + validatorsManager.validate() + } + + with(configuration.state) { + if (targetPlatformKind == null) { + targetPlatformKind = getDefaultTargetPlatform() + } + + if (languageLevel == null) { + languageLevel = getDefaultLanguageLevel() + } + } + + reset() + } + + override fun isModified(): Boolean { + return languageVersionComboBox.selectedItem != configuration.state.languageLevel + || targetPlatformComboBox.selectedItem != configuration.state.targetPlatformKind + } + + override fun reset() { + languageVersionComboBox.selectedItem = configuration.state.languageLevel + targetPlatformComboBox.selectedItem = configuration.state.targetPlatformKind + } + + override fun apply() { + configuration.state.languageLevel = languageVersionComboBox.selectedItem as KotlinFacetConfiguration.LanguageLevel? + configuration.state.targetPlatformKind = targetPlatformComboBox.selectedItem as KotlinFacetConfiguration.TargetPlatform? + } + + override fun getDisplayName() = "Kotlin" + + override fun createComponent(): JComponent { + val mainPanel = JPanel(BorderLayout()) + val contentPanel = FormBuilder + .createFormBuilder() + .addLabeledComponent("&Language version: ", languageVersionComboBox) + .addLabeledComponent("&Target platform: ", targetPlatformComboBox) + .panel + mainPanel.add(contentPanel, BorderLayout.NORTH) + return mainPanel + } + + override fun disposeUIResources() { + + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetType.kt b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetType.kt new file mode 100644 index 00000000000..f48229db128 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/facet/KotlinFacetType.kt @@ -0,0 +1,51 @@ +/* + * 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.facet.Facet +import com.intellij.facet.FacetType +import com.intellij.facet.FacetTypeId +import com.intellij.facet.FacetTypeRegistry +import com.intellij.openapi.module.JavaModuleType +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleType +import org.jetbrains.kotlin.idea.KotlinIcons +import javax.swing.Icon + +class KotlinFacetType : FacetType(TYPE_ID, ID, NAME) { + companion object { + val TYPE_ID = FacetTypeId("kotlin-language") + val ID = "kotlin-language" + val NAME = "Kotlin" + + val INSTANCE: KotlinFacetType + get() = FacetTypeRegistry.getInstance().findFacetType(TYPE_ID) as KotlinFacetType + } + + override fun isSuitableModuleType(moduleType: ModuleType<*>) = moduleType is JavaModuleType + + override fun getIcon(): Icon = KotlinIcons.SMALL_LOGO + + override fun createDefaultConfiguration() = KotlinFacetConfiguration() + + override fun createFacet( + module: Module, + name: String, + configuration: KotlinFacetConfiguration, + underlyingFacet: Facet<*>? + ) = KotlinFacet(module, name, configuration) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/util/DescriptionAware.kt b/idea/src/org/jetbrains/kotlin/idea/util/DescriptionAware.kt new file mode 100644 index 00000000000..ed35e497448 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/util/DescriptionAware.kt @@ -0,0 +1,21 @@ +/* + * 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.util + +interface DescriptionAware { + val description: String +}