diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/CreateKotlinSubClassIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/CreateKotlinSubClassIntention.kt index b1e6df0fe6d..ca83af007e5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/CreateKotlinSubClassIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/CreateKotlinSubClassIntention.kt @@ -14,12 +14,14 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.JavaDirectoryService import com.intellij.refactoring.rename.PsiElementRenameHandler +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.DescriptorVisibility import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler +import org.jetbrains.kotlin.idea.project.languageVersionSettings import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile import org.jetbrains.kotlin.idea.refactoring.ui.CreateKotlinClassDialog import org.jetbrains.kotlin.idea.util.application.runWriteAction @@ -73,8 +75,8 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention( if (editor == null) throw IllegalArgumentException("This intention requires an editor") val name = element.name ?: throw IllegalStateException("This intention should not be applied to anonymous classes") - if (element.isSealed()) { - createSealedSubclass(element, name, editor) + if (element.isSealed() && !element.languageVersionSettings.supportsFeature(LanguageFeature.SealedInterfaces)) { + createNestedSubclass(element, name, editor) } else { createExternalSubclass(element, name, editor) } @@ -87,7 +89,7 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention( private fun targetNameWithoutConflicts(baseName: String, container: KtClassOrObject?) = KotlinNameSuggester.suggestNameByName(defaultTargetName(baseName)) { container?.hasSameDeclaration(it) != true } - private fun createSealedSubclass(sealedClass: KtClass, sealedName: String, editor: Editor) { + private fun createNestedSubclass(sealedClass: KtClass, sealedName: String, editor: Editor) { val project = sealedClass.project val klass = runWriteAction { val builder = buildClassHeader(targetNameWithoutConflicts(sealedName, sealedClass), sealedClass, sealedName) @@ -162,7 +164,8 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention( targetNameWithoutConflicts(baseName, baseClass.containingClassOrObject), aPackage?.qualifiedName ?: "", CreateClassKind.CLASS, true, - ModuleUtilCore.findModuleForPsiElement(baseClass) + ModuleUtilCore.findModuleForPsiElement(baseClass), + baseClass.isSealed() ) { override fun getBaseDir(packageName: String?) = sourceDir diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/CreateKotlinClassDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/CreateKotlinClassDialog.kt index fe8752d64dc..6ca27f204b2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/CreateKotlinClassDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/CreateKotlinClassDialog.kt @@ -53,7 +53,8 @@ open class CreateKotlinClassDialog( targetPackageName: String, kind: ClassKind, private val myClassNameEditable: Boolean, - private val myModule: Module? + private val myModule: Module?, + val isSealed: Boolean = false ) : DialogWrapper(myProject, true) { private val myInformationLabel = JLabel("#") @@ -63,7 +64,9 @@ open class CreateKotlinClassDialog( myProject, RECENTS_KEY, CodeInsightBundle.message("dialog.create.class.package.chooser.title") - ) + ).also { + if (isSealed) it.isEnabled = false + } private val myTfClassName: JTextField = MyTextField() var targetDirectory: PsiDirectory? = null private set @@ -239,6 +242,6 @@ open class CreateKotlinClassDialog( override fun pass(s: String?) { setErrorText(s, myDestinationCB) } - }, myPackageComponent.getChildComponent()) + }, myPackageComponent.childComponent, isSealed) } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/KotlinDestinationFolderComboBox.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/KotlinDestinationFolderComboBox.java index bc3a85da6b9..f82c1f342af 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/KotlinDestinationFolderComboBox.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/ui/KotlinDestinationFolderComboBox.java @@ -28,6 +28,7 @@ import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDest import com.intellij.ui.*; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.idea.KotlinBundle; +import org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt; import javax.swing.*; import java.awt.*; @@ -72,12 +73,25 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse } public void setData( - Project project, - PsiDirectory initialTargetDirectory, - Pass errorMessageUpdater, EditorComboBox editorComboBox + Project project, + PsiDirectory initialTargetDirectory, + Pass errorMessageUpdater, EditorComboBox editorComboBox + ) { + setData(project, initialTargetDirectory, errorMessageUpdater, editorComboBox, false); + } + + public void setData(Project project, PsiDirectory initialTargetDirectory, Pass errorMessageUpdater, + EditorComboBox editorComboBox, boolean sourceRootsInTargetDirOnly ) { myInitialTargetDirectory = initialTargetDirectory; - mySourceRoots = getKotlinAwareDestinationSourceRoots(project); + + if (sourceRootsInTargetDirOnly) { + Module module = ModuleUtilCore.findModuleForFile(myInitialTargetDirectory.getVirtualFile(), project); + mySourceRoots = ProjectRootUtilsKt.collectKotlinAwareDestinationSourceRoots(module); + } else { + mySourceRoots = getKotlinAwareDestinationSourceRoots(project); + } + new ComboboxSpeedSearch(getComboBox()) { @Override protected String getElementText(Object element) { @@ -130,7 +144,8 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse return; } } - setComboboxModel(getComboBox(), root, root, fileIndex, mySourceRoots, project, true, errorMessageUpdater); + setComboboxModel(getComboBox(), root, root, fileIndex, mySourceRoots, project, true, errorMessageUpdater, + sourceRootsInTargetDirOnly); }); editorComboBox.addDocumentListener(new DocumentListener() { @@ -140,10 +155,11 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) comboBox.getSelectedItem(); setComboboxModel(comboBox, selectedItem != null && selectedItem != NULL_WRAPPER ? fileIndex .getSourceRootForFile(selectedItem.getDirectory().getVirtualFile()) : initialSourceRoot, selection[0], fileIndex, - mySourceRoots, project, false, errorMessageUpdater); + mySourceRoots, project, false, errorMessageUpdater, sourceRootsInTargetDirOnly); } }); - setComboboxModel(getComboBox(), initialSourceRoot, selection[0], fileIndex, mySourceRoots, project, false, errorMessageUpdater); + setComboboxModel(getComboBox(), initialSourceRoot, selection[0], fileIndex, mySourceRoots, project, false, errorMessageUpdater, + sourceRootsInTargetDirOnly); getComboBox().addActionListener(e -> { Object selectedItem = getComboBox().getSelectedItem(); updateErrorMessage(errorMessageUpdater, fileIndex, selectedItem); @@ -199,7 +215,8 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse List sourceRoots, Project project, boolean forceIncludeAll, - Pass updateErrorMessage + Pass updateErrorMessage, + boolean sourceRootsInTargetDirOnly ) { LinkedHashSet targetDirectories = new LinkedHashSet<>(); HashMap pathsToCreate = new HashMap<>(); @@ -224,7 +241,7 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse oldOne = itemWrapper; } } - if (oldSelection == null || !fileIndex.isInLibrarySource(oldSelection)) { + if (!sourceRootsInTargetDirOnly && (oldSelection == null || !fileIndex.isInLibrarySource(oldSelection))) { items.add(NULL_WRAPPER); } DirectoryChooser.ItemWrapper selection = chooseSelection(initialTargetDirectorySourceRoot, fileIndex, items, initial, oldOne); diff --git a/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt b/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt index 221d492e0bd..5eb63a27dd1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/roots/projectRootUtils.kt @@ -99,7 +99,7 @@ fun getKotlinAwareDestinationSourceRoots(project: Project): List { private val KOTLIN_AWARE_SOURCE_ROOT_TYPES: Set> = JavaModuleSourceRootTypes.SOURCES + ALL_KOTLIN_SOURCE_ROOT_TYPES -private fun Module.collectKotlinAwareDestinationSourceRoots(): List { +fun Module.collectKotlinAwareDestinationSourceRoots(): List { return rootManager .contentEntries .asSequence() diff --git a/idea/testData/quickfix/implement/sealedAfter15.kt b/idea/testData/quickfix/implement/sealedAfter15.kt new file mode 100644 index 00000000000..7e5eef76739 --- /dev/null +++ b/idea/testData/quickfix/implement/sealedAfter15.kt @@ -0,0 +1,8 @@ +// "Implement sealed class" "true" +// WITH_RUNTIME +// SHOULD_BE_AVAILABLE_AFTER_EXECUTION +// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces + +sealed class Base { + abstract fun foo(): Int +} \ No newline at end of file diff --git a/idea/testData/quickfix/implement/sealedAfter15.kt.after b/idea/testData/quickfix/implement/sealedAfter15.kt.after new file mode 100644 index 00000000000..d3632cea1b0 --- /dev/null +++ b/idea/testData/quickfix/implement/sealedAfter15.kt.after @@ -0,0 +1,14 @@ +// "Implement sealed class" "true" +// WITH_RUNTIME +// SHOULD_BE_AVAILABLE_AFTER_EXECUTION +// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces + +sealed class Base { + abstract fun foo(): Int +} + +class BaseImpl : Base() { + override fun foo(): Int { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/implement/sealedEmptyAfter15.kt b/idea/testData/quickfix/implement/sealedEmptyAfter15.kt new file mode 100644 index 00000000000..ac81425b417 --- /dev/null +++ b/idea/testData/quickfix/implement/sealedEmptyAfter15.kt @@ -0,0 +1,4 @@ +// "Implement sealed class" "true" +// SHOULD_BE_AVAILABLE_AFTER_EXECUTION +// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces +sealed class Sealed \ No newline at end of file diff --git a/idea/testData/quickfix/implement/sealedEmptyAfter15.kt.after b/idea/testData/quickfix/implement/sealedEmptyAfter15.kt.after new file mode 100644 index 00000000000..5b417865afd --- /dev/null +++ b/idea/testData/quickfix/implement/sealedEmptyAfter15.kt.after @@ -0,0 +1,5 @@ +// "Implement sealed class" "true" +// SHOULD_BE_AVAILABLE_AFTER_EXECUTION +// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces +sealed class Sealed +class SealedImpl : Sealed() \ No newline at end of file diff --git a/idea/testData/quickfix/implement/sealedWithConflictAfter15.kt b/idea/testData/quickfix/implement/sealedWithConflictAfter15.kt new file mode 100644 index 00000000000..e97f24cebea --- /dev/null +++ b/idea/testData/quickfix/implement/sealedWithConflictAfter15.kt @@ -0,0 +1,12 @@ +// "Implement sealed class" "true" +// WITH_RUNTIME +// SHOULD_BE_AVAILABLE_AFTER_EXECUTION +// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces + +sealed class Base { + abstract fun foo(): Int + + class BaseImpl : Base() { + override fun foo() = throw UnsupportedOperationException() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/implement/sealedWithConflictAfter15.kt.after b/idea/testData/quickfix/implement/sealedWithConflictAfter15.kt.after new file mode 100644 index 00000000000..d7d02baa8b8 --- /dev/null +++ b/idea/testData/quickfix/implement/sealedWithConflictAfter15.kt.after @@ -0,0 +1,18 @@ +// "Implement sealed class" "true" +// WITH_RUNTIME +// SHOULD_BE_AVAILABLE_AFTER_EXECUTION +// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces + +sealed class Base { + abstract fun foo(): Int + + class BaseImpl : Base() { + override fun foo() = throw UnsupportedOperationException() + } +} + +class BaseImpl : Base() { + override fun foo(): Int { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index b8d2fb3d472..5ebe7a341de 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -8204,16 +8204,31 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/implement/sealed.kt"); } + @TestMetadata("sealedAfter15.kt") + public void testSealedAfter15() throws Exception { + runTest("idea/testData/quickfix/implement/sealedAfter15.kt"); + } + @TestMetadata("sealedEmpty.kt") public void testSealedEmpty() throws Exception { runTest("idea/testData/quickfix/implement/sealedEmpty.kt"); } + @TestMetadata("sealedEmptyAfter15.kt") + public void testSealedEmptyAfter15() throws Exception { + runTest("idea/testData/quickfix/implement/sealedEmptyAfter15.kt"); + } + @TestMetadata("sealedWithConflict.kt") public void testSealedWithConflict() throws Exception { runTest("idea/testData/quickfix/implement/sealedWithConflict.kt"); } + @TestMetadata("sealedWithConflictAfter15.kt") + public void testSealedWithConflictAfter15() throws Exception { + runTest("idea/testData/quickfix/implement/sealedWithConflictAfter15.kt"); + } + @TestMetadata("typeParameter.kt") public void testTypeParameter() throws Exception { runTest("idea/testData/quickfix/implement/typeParameter.kt");