diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/internal/refactoringTesting/cases/MoveKotlinDeclarationsHandlerTestActions.kt b/idea/src/org/jetbrains/kotlin/idea/actions/internal/refactoringTesting/cases/MoveKotlinDeclarationsHandlerTestActions.kt index e5f738e0a24..bd4eabd2ecf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/actions/internal/refactoringTesting/cases/MoveKotlinDeclarationsHandlerTestActions.kt +++ b/idea/src/org/jetbrains/kotlin/idea/actions/internal/refactoringTesting/cases/MoveKotlinDeclarationsHandlerTestActions.kt @@ -64,9 +64,7 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep "isSearchReferences = $isSearchReferences\n" + "isSearchInComments = $isSearchInComments\n" + "isSearchInNonJavaFiles = $isSearchInNonJavaFiles\n" + - "isDeleteEmptyFiles = $isDeleteEmptyFiles\n" + - "isUpdatePackageDirective = $isUpdatePackageDirective\n" + - "isFullFileMove = $isFullFileMove" + "isDeleteEmptyFiles = $isDeleteEmptyFiles\n" } private fun KotlinAwareMoveFilesOrDirectoriesModel.testDataString(): String { @@ -153,10 +151,6 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep targetDirectory: PsiDirectory?, targetFile: KtFile?, moveToPackage: Boolean, - searchInComments: Boolean, - searchForTextOccurrences: Boolean, - deleteEmptySourceFiles: Boolean, - moveMppDeclarations: Boolean, moveCallback: MoveCallback? ) { val selectedElementsToMove = mutableSetOf() @@ -180,8 +174,6 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep isSearchInComments = randomBoolean(), isSearchInNonJavaFiles = randomBoolean(), isDeleteEmptyFiles = randomBoolean(), - isUpdatePackageDirective = randomBoolean(), - isFullFileMove = randomBoolean(), applyMPPDeclarations = true, moveCallback = null ) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsHandler.kt index e6a644a3dd8..e13c2d8d9bc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsHandler.kt @@ -68,10 +68,6 @@ private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerAction targetDirectory: PsiDirectory?, targetFile: KtFile?, moveToPackage: Boolean, - searchInComments: Boolean, - searchForTextOccurrences: Boolean, - deleteEmptySourceFiles: Boolean, - moveMppDeclarations: Boolean, moveCallback: MoveCallback? ) = MoveKotlinTopLevelDeclarationsDialog( project, @@ -80,10 +76,6 @@ private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerAction targetDirectory, targetFile, moveToPackage, - searchInComments, - searchForTextOccurrences, - deleteEmptySourceFiles, - moveMppDeclarations, moveCallback ).show() @@ -180,6 +172,15 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi } val initialTargetDirectory = MoveFilesOrDirectoriesUtil.resolveToDirectory(project, initialTargetElement) + if (!ApplicationManager.getApplication().isUnitTestMode && elementsToSearch.any { it.isExpectDeclaration() || it.isEffectivelyActual() } + ) { + val message = RefactoringBundle.getCannotRefactorMessage( + "This refactoring will move selected declaration without it's expect/actual counterparts that may lead to compilation errors.\n\"Do you wish to proceed?\"") + val title = RefactoringBundle.getCannotRefactorMessage("MPP declarations does not supported by this refactoring.") + val proceedWithIncompleteRefactoring = Messages.showYesNoDialog(project, message, title, Messages.getWarningIcon()) + if (proceedWithIncompleteRefactoring != Messages.YES) return true + } + handlerActions.invokeKotlinAwareMoveFilesOrDirectoriesRefactoring( project, initialTargetDirectory, ktFileElements, callback ) @@ -193,10 +194,6 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi val targetDirectory = if (targetContainer != null) { MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements) } else null - val searchInComments = KotlinRefactoringSettings.instance.MOVE_SEARCH_IN_COMMENTS - val searchInText = KotlinRefactoringSettings.instance.MOVE_SEARCH_FOR_TEXT - val deleteEmptySourceFiles = KotlinRefactoringSettings.instance.MOVE_DELETE_EMPTY_SOURCE_FILES - val moveMppDeclarations = KotlinRefactoringSettings.instance.MOVE_MPP_DECLARATIONS val targetFile = targetContainer as? KtFile val moveToPackage = targetContainer !is KtFile @@ -207,10 +204,6 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi targetDirectory, targetFile, moveToPackage, - searchInComments, - searchInText, - deleteEmptySourceFiles, - moveMppDeclarations, callback ) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsHandlerActions.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsHandlerActions.kt index 5b76f4a7b3b..384e75d0988 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsHandlerActions.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsHandlerActions.kt @@ -31,10 +31,6 @@ internal interface MoveKotlinDeclarationsHandlerActions { targetDirectory: PsiDirectory?, targetFile: KtFile?, moveToPackage: Boolean, - searchInComments: Boolean, - searchForTextOccurrences: Boolean, - deleteEmptySourceFiles: Boolean, - moveMppDeclarations: Boolean, moveCallback: MoveCallback? ) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt index 6e6e283a5fc..938b0dab1e2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt @@ -363,7 +363,7 @@ class MoveKotlinDeclarationsProcessor( } } - if (descriptor.deleteSourceFiles) { + if (descriptor.deleteSourceFiles && sourceFile.declarations.isEmpty()) { sourceFile.delete() } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveToKotlinFileProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveToKotlinFileProcessor.kt index 08268dfa683..50cd2443b57 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveToKotlinFileProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveToKotlinFileProcessor.kt @@ -66,22 +66,19 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor( return super.showConflicts(conflicts, usages) } - // Assign a temporary name to file-under-move to avoid naming conflict during the refactoring - private fun renameFileTemporarily() { - if (targetDirectory.findFile(targetFileName) == null) return + private fun renameFileTemporarilyIfNeeded() { + if (targetDirectory.findFile(sourceFile.name) == null) return + + val containingDirectory = sourceFile.containingDirectory ?: return val temporaryName = UniqueNameGenerator.generateUniqueName("temp", "", ".kt") { - sourceFile.containingDirectory!!.findFile(it) == null + containingDirectory.findFile(it) == null } sourceFile.name = temporaryName } override fun performRefactoring(usages: Array) { - val needTemporaryRename = targetDirectory.findFile(sourceFile.name) != null - if (needTemporaryRename) { - renameFileTemporarily() - } - + renameFileTemporarilyIfNeeded() super.performRefactoring(usages) sourceFile.name = targetFileName } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt index ebfaa3efa3e..ab07319253d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt @@ -129,9 +129,11 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( if (targetContainer is KtClassOrObject) { val targetClass = targetContainer as KtClassOrObject? - for (member in targetClass!!.declarations) { - if (member is KtClassOrObject && className == member.getName()) { - throw ConfigurationException(RefactoringBundle.message("inner.class.exists", className, targetClass.name)) + if (targetClass != null) { + for (member in targetClass.declarations) { + if (member is KtClassOrObject && className == member.getName()) { + throw ConfigurationException(RefactoringBundle.message("inner.class.exists", className, targetClass.name)) + } } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form index 8ee34622e3a..d254ace870b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.form @@ -62,6 +62,15 @@ + + + + + + + + + @@ -72,16 +81,6 @@ - - - - - - - - - - @@ -107,15 +106,6 @@ - - - - - - - - - diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java index e3861db0fea..98c855a8bd4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsDialog.java @@ -16,7 +16,6 @@ import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.classMembers.AbstractMemberInfoModel; import com.intellij.refactoring.classMembers.MemberInfoBase; import com.intellij.refactoring.classMembers.MemberInfoChange; -import com.intellij.refactoring.classMembers.MemberInfoChangeListener; import com.intellij.refactoring.move.MoveCallback; import com.intellij.refactoring.move.MoveHandler; import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo; @@ -31,7 +30,6 @@ import kotlin.collections.CollectionsKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.idea.KotlinBundle; -import org.jetbrains.kotlin.idea.core.PackageUtilsKt; import org.jetbrains.kotlin.idea.core.util.PhysicalFileSystemUtilsKt; import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringSettings; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo; @@ -49,8 +47,10 @@ import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import javax.swing.*; import java.awt.*; import java.io.File; -import java.util.*; +import java.util.BitSet; +import java.util.Collection; import java.util.List; +import java.util.Set; import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getSuitableDestinationSourceRoots; @@ -69,44 +69,31 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { private TextFieldWithBrowseButton fileChooser; private JPanel memberInfoPanel; private JTextField tfFileNameInPackage; - private JCheckBox cbSpecifyFileNameInPackage; - private JCheckBox cbUpdatePackageDirective; private JCheckBox cbDeleteEmptySourceFiles; private JCheckBox cbSearchReferences; private JCheckBox cbApplyMPPDeclarationsMove; private KotlinMemberSelectionTable memberTable; - private class MemberSelectionerInfoChangeListener implements MemberInfoChangeListener { - - private final List memberInfos; - - public MemberSelectionerInfoChangeListener(List memberInfos) { - this.memberInfos = memberInfos; - } - - private boolean shouldUpdateFileNameField(Collection changedMembers) { - if (!tfFileNameInPackage.isEnabled()) return true; - - Collection previousDeclarations = CollectionsKt.filterNotNull( - CollectionsKt.map( - memberInfos, - info -> changedMembers.contains(info) != info.isChecked() ? info.getMember() : null - ) - ); - String suggestedText = previousDeclarations.isEmpty() ? "" : MoveUtilsKt.guessNewFileName(previousDeclarations); - return tfFileNameInPackage.getText().equals(suggestedText); - } - - @Override - public void memberInfoChanged(@NotNull MemberInfoChange event) { - updateControls(); - updatePackageDirectiveCheckBox(); - updateFileNameInPackageField(); - // Update file name field only if it user hasn't changed it to some non-default value - if (shouldUpdateFileNameField(event.getChangedMembers())) { - updateSuggestedFileName(); - } - } + public MoveKotlinTopLevelDeclarationsDialog( + @NotNull Project project, + @NotNull Set elementsToMove, + @Nullable String targetPackageName, + @Nullable PsiDirectory targetDirectory, + @Nullable KtFile targetFile, + boolean moveToPackage, + @Nullable MoveCallback moveCallback + ) { + this(project, + elementsToMove, + targetPackageName, + targetDirectory, + targetFile, + moveToPackage, + KotlinRefactoringSettings.getInstance().MOVE_SEARCH_IN_COMMENTS, + KotlinRefactoringSettings.getInstance().MOVE_SEARCH_FOR_TEXT, + KotlinRefactoringSettings.getInstance().MOVE_DELETE_EMPTY_SOURCE_FILES, + KotlinRefactoringSettings.getInstance().MOVE_MPP_DECLARATIONS, + moveCallback); } public MoveKotlinTopLevelDeclarationsDialog( @@ -124,16 +111,19 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { ) { super(project, true); + init(); + List sourceFiles = getSourceFiles(elementsToMove); this.moveCallback = moveCallback; this.initialTargetDirectory = targetDirectory; - init(); setTitle(MoveHandler.REFACTORING_NAME); - initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles, moveMppDeclarations); + List allDeclarations = getAllDeclarations(sourceFiles); + + initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles, moveMppDeclarations, allDeclarations); initPackageChooser(targetPackageName, targetDirectory, sourceFiles); @@ -141,25 +131,28 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { initMoveToButtons(moveToPackage); - initMemberInfo(elementsToMove, sourceFiles); + initMemberInfo(elementsToMove, allDeclarations); updateControls(); initializedCheckBoxesState = getCheckboxesState(true); } + @Override + protected void init() { + super.init(); + } + private final BitSet initializedCheckBoxesState; private BitSet getCheckboxesState(boolean applyDefaults) { - BitSet state = new BitSet(7); + BitSet state = new BitSet(5); state.set(0, applyDefaults || cbSearchInComments.isSelected()); //cbSearchInComments default is true state.set(1, applyDefaults || cbSearchTextOccurrences.isSelected()); //cbSearchTextOccurrences default is true state.set(2, applyDefaults || cbDeleteEmptySourceFiles.isSelected()); //cbDeleteEmptySourceFiles default is true state.set(3, applyDefaults || cbApplyMPPDeclarationsMove.isSelected()); //cbApplyMPPDeclarationsMove default is true - state.set(4, cbSpecifyFileNameInPackage.isSelected()); - state.set(5, cbUpdatePackageDirective.isSelected()); - state.set(6, cbSearchReferences.isSelected()); + state.set(4, cbSearchReferences.isSelected()); return state; } @@ -183,32 +176,24 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { ); } - private static boolean arePackagesAndDirectoryMatched(List sourceFiles) { - for (KtFile sourceFile : sourceFiles) { - if (!PackageUtilsKt.packageMatchesDirectoryOrImplicit(sourceFile)) return false; - } - return true; - } - private void initMemberInfo( @NotNull Set elementsToMove, - @NotNull List sourceFiles + @NotNull List declarations ) { - List memberInfos = CollectionsKt.map( - getAllDeclarations(sourceFiles), - declaration -> { - KotlinMemberInfo memberInfo = new KotlinMemberInfo(declaration, false); - memberInfo.setChecked(elementsToMove.contains(declaration)); - return memberInfo; - } - ); + ////KotlinMemberInfo run resolve on declaration so it is good to place it to the process + List memberInfos = MoveUtilsKt.mapWithReadActionInProcess(declarations, myProject, MoveHandler.REFACTORING_NAME, (declaration) -> { + KotlinMemberInfo memberInfo = new KotlinMemberInfo(declaration, false); + memberInfo.setChecked(elementsToMove.contains(declaration)); + return memberInfo; + }); + KotlinMemberSelectionPanel selectionPanel = new KotlinMemberSelectionPanel(getTitle(), memberInfos, null); memberTable = selectionPanel.getTable(); MemberInfoModelImpl memberInfoModel = new MemberInfoModelImpl(); memberInfoModel.memberInfoChanged(new MemberInfoChange<>(memberInfos)); selectionPanel.getTable().setMemberInfoModel(memberInfoModel); selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel); - selectionPanel.getTable().addMemberInfoChangeListener(new MemberSelectionerInfoChangeListener(memberInfos)); + selectionPanel.getTable().addMemberInfoChangeListener(listener -> updateControls()); cbApplyMPPDeclarationsMove.addChangeListener(e -> updateControls()); memberInfoPanel.add(selectionPanel, BorderLayout.CENTER); } @@ -219,8 +204,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { private void updateFileNameInPackageField() { boolean movingSingleFileToPackage = rbMoveToPackage.isSelected() && getSourceFiles(getSelectedElementsToMove()).size() == 1; - cbSpecifyFileNameInPackage.setEnabled(movingSingleFileToPackage); - tfFileNameInPackage.setEnabled(movingSingleFileToPackage && cbSpecifyFileNameInPackage.isSelected()); + tfFileNameInPackage.setEnabled(movingSingleFileToPackage); } private void initPackageChooser( @@ -243,17 +227,20 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { }, classPackageChooser.getChildComponent() ); - - cbSpecifyFileNameInPackage.addActionListener(e -> updateFileNameInPackageField()); - - cbUpdatePackageDirective.setSelected(arePackagesAndDirectoryMatched(sourceFiles)); } - private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences, boolean deleteEmptySourceFiles, boolean moveMppDeclarations) { + private void initSearchOptions( + boolean searchInComments, + boolean searchForTextOccurences, + boolean deleteEmptySourceFiles, + boolean moveMppDeclarations, + List allDeclarations + ) { cbSearchInComments.setSelected(searchInComments); cbSearchTextOccurrences.setSelected(searchForTextOccurences); cbDeleteEmptySourceFiles.setSelected(deleteEmptySourceFiles); cbApplyMPPDeclarationsMove.setSelected(moveMppDeclarations); + cbApplyMPPDeclarationsMove.setVisible(isMPPDeclarationInList(allDeclarations)); } private void initMoveToButtons(boolean moveToPackage) { @@ -348,8 +335,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { ); } - private boolean isMppDeclarationSelected() { - for(KtNamedDeclaration element : getSelectedElementsToMove()) { + private boolean isMPPDeclarationInList(List declarations) { + for(KtNamedDeclaration element : declarations) { if (ExpectActualUtilKt.isEffectivelyActual(element, true) || ExpectActualUtilKt.isExpectDeclaration(element)) { return true; @@ -358,6 +345,10 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { return false; } + private boolean isMppDeclarationSelected() { + return isMPPDeclarationInList(getSelectedElementsToMove()); + } + private void updateControls() { boolean mppDeclarationSelected = isMppDeclarationSelected(); @@ -376,27 +367,11 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { classPackageChooser.setEnabled(moveToPackage); updateFileNameInPackageField(); fileChooser.setEnabled(!moveToPackage); - updatePackageDirectiveCheckBox(); - UIUtil.setEnabled(targetPanel, moveToPackage && hasAnySourceRoots(), true); + UIUtil.setEnabled(targetPanel, moveToPackage && !needToMoveMPPDeclarations && hasAnySourceRoots(), true); updateSuggestedFileName(); myHelpAction.setEnabled(false); } - private boolean isFullFileMove() { - Map> fileToElements = CollectionsKt.groupBy( - getSelectedElementsToMove(), - KtPureElement::getContainingKtFile - ); - for (Map.Entry> entry : fileToElements.entrySet()) { - if (KtPsiUtilKt.getFileOrScriptDeclarations(entry.getKey()).size() != entry.getValue().size()) return false; - } - return true; - } - - private void updatePackageDirectiveCheckBox() { - cbUpdatePackageDirective.setEnabled(rbMoveToPackage.isSelected() && isFullFileMove()); - } - private boolean hasAnySourceRoots() { return !getSuitableDestinationSourceRoots(myProject).isEmpty(); } @@ -443,11 +418,14 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { private MoveKotlinTopLevelDeclarationsModel getModel() throws ConfigurationException { - DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem(); - PsiDirectory selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory; - boolean mppDeclarationSelected = cbApplyMPPDeclarationsMove.isSelected() && isMppDeclarationSelected(); + PsiDirectory selectedPsiDirectory = null; + if (!mppDeclarationSelected) { + DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem(); + selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory; + } + List selectedElements = getSelectedElementsToMoveChecked(); return new MoveKotlinTopLevelDeclarationsModel( @@ -462,8 +440,6 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog { cbSearchInComments.isSelected(), cbSearchTextOccurrences.isSelected(), cbDeleteEmptySourceFiles.isSelected(), - cbUpdatePackageDirective.isSelected(), - isFullFileMove(), mppDeclarationSelected, moveCallback ); diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt index 3cf0a46c831..e4eeb6947f1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt @@ -16,6 +16,7 @@ import com.intellij.refactoring.BaseRefactoringProcessor import com.intellij.refactoring.MoveDestination import com.intellij.refactoring.PackageWrapper import com.intellij.refactoring.move.MoveCallback +import com.intellij.refactoring.move.MoveHandler import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination import org.jetbrains.kotlin.idea.KotlinBundle @@ -25,18 +26,17 @@ import org.jetbrains.kotlin.idea.core.util.toPsiDirectory import org.jetbrains.kotlin.idea.core.util.toPsiFile import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory +import org.jetbrains.kotlin.idea.refactoring.move.mapWithReadActionInProcess import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.* import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective -import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MoveRefactoringDestination +import org.jetbrains.kotlin.idea.statistics.MoveRefactoringFUSCollector.MovedEntity import org.jetbrains.kotlin.idea.util.collectAllExpectAndActualDeclaration import org.jetbrains.kotlin.idea.util.isEffectivelyActual import org.jetbrains.kotlin.idea.util.isExpectDeclaration import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtFunction -import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getFileOrScriptDeclarations import java.io.File import java.nio.file.InvalidPathException import java.nio.file.Paths @@ -53,8 +53,6 @@ internal class MoveKotlinTopLevelDeclarationsModel( val isSearchInComments: Boolean, val isSearchInNonJavaFiles: Boolean, val isDeleteEmptyFiles: Boolean, - val isUpdatePackageDirective: Boolean, - val isFullFileMove: Boolean, val applyMPPDeclarations: Boolean, val moveCallback: MoveCallback? ) : Model { @@ -65,9 +63,13 @@ internal class MoveKotlinTopLevelDeclarationsModel( private fun checkedGetSourceDirectory() = sourceFiles.mapToSingleOrNull { it.parent } ?: throw ConfigurationException(KotlinBundle.message("text.cannot.determine.source.directory")) - private val sourceFiles: Set = elementsToMove.mapTo(mutableSetOf()) { it.containingKtFile } + private val sourceFiles: Set by lazy { + elementsToMove.mapTo(mutableSetOf()) { it.containingKtFile } + } - private val elementsToMoveHasMPP = applyMPPDeclarations && elementsToMove.any { it.isEffectivelyActual() || it.isExpectDeclaration() } + private val elementsToMoveHasMPP by lazy { + applyMPPDeclarations && elementsToMove.any { it.isEffectivelyActual() || it.isExpectDeclaration() } + } private val singleSourceFileMode = sourceFiles.size == 1 @@ -121,15 +123,17 @@ internal class MoveKotlinTopLevelDeclarationsModel( } private fun selectMoveTargetToPackage(): KotlinMoveTarget { - require(sourceFiles.isNotEmpty()) - val moveDestination = selectPackageBasedDestination() - val targetDirectory: PsiDirectory? = moveDestination.getTargetIfExists(checkedGetSourceDirectory()) - val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null - - if (targetDirectory != null) { - tryMoveToPackageForExistingDirectory(targetFileName, targetDirectory)?.let { return it } + val targetDirectory: PsiDirectory? + if (!elementsToMoveHasMPP) { + targetDirectory = moveDestination.getTargetIfExists(checkedGetSourceDirectory()) + val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null + if (targetDirectory != null) { + tryMoveToPackageForExistingDirectory(targetFileName, targetDirectory)?.let { return it } + } + } else { + targetDirectory = null } return KotlinMoveTargetForDeferredFile( @@ -193,9 +197,6 @@ internal class MoveKotlinTopLevelDeclarationsModel( } } - private fun selectMoveTarget() = - if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile() - private fun verifyBeforeRun() { if (!isMoveToPackage && elementsToMoveHasMPP) throw ConfigurationException(KotlinBundle.message("text.cannot.move.expect.actual.declaration.to.file")) @@ -217,14 +218,7 @@ internal class MoveKotlinTopLevelDeclarationsModel( } } - @Throws(ConfigurationException::class) - override fun computeModelResult() = computeModelResult(throwOnConflicts = false) - - @Throws(ConfigurationException::class) - override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData { - - verifyBeforeRun() - + private fun getFUSParameters(): Pair { val classType = if (elementsToMoveHasMPP) MovedEntity.MPPCLASSES else MovedEntity.CLASSES val functionType = if (elementsToMoveHasMPP) MovedEntity.MPPFUNCTIONS else MovedEntity.FUNCTIONS val mixedType = if (elementsToMoveHasMPP) MovedEntity.MPPMIXED else MovedEntity.MIXED @@ -236,41 +230,58 @@ internal class MoveKotlinTopLevelDeclarationsModel( val destination = if (isMoveToPackage) MoveRefactoringDestination.PACKAGE else MoveRefactoringDestination.FILE - if (isFullFileMove && isMoveToPackage) { - tryMoveFile(throwOnConflicts)?.let { - return ModelResultWithFUSData(it, elementsToMove.size, entity, destination) - } - } + return entity to destination + } + + + @Throws(ConfigurationException::class) + override fun computeModelResult() = computeModelResult(throwOnConflicts = false) + + @Throws(ConfigurationException::class) + override fun computeModelResult(throwOnConflicts: Boolean): ModelResultWithFUSData { + + verifyBeforeRun() + + val (entity, destination) = getFUSParameters() + + val processor = tryMoveEntireFile(throwOnConflicts) ?: moveDeclaration(throwOnConflicts) - val processor = moveDeclaration(throwOnConflicts) return ModelResultWithFUSData(processor, elementsToMove.size, entity, destination) } - private fun tryMoveFile(throwOnConflicts: Boolean): BaseRefactoringProcessor? { + private fun tryMoveEntireFile(throwOnConflicts: Boolean): BaseRefactoringProcessor? { - if (elementsToMoveHasMPP) return null + if (!isDeleteEmptyFiles || elementsToMoveHasMPP || !isMoveToPackage) return null - val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage - if (targetFileName != null) checkTargetFileName(targetFileName) + val allDeclarationsMovingOut = elementsToMove + .groupBy { obj: KtPureElement -> obj.containingKtFile } + .all { it.key.getFileOrScriptDeclarations().size == it.value.size } + if (!allDeclarationsMovingOut) return null - val moveDestination = selectPackageBasedDestination() - val targetDirectory = moveDestination.getTargetIfExists(checkedGetSourceDirectory()) ?: return null + val targetDirectory = selectPackageBasedDestination() + .getTargetIfExists(checkedGetSourceDirectory()) + ?: return null - val filesExistingInTargetDir = getFilesExistingInTargetDirectory(targetFileName, targetDirectory) + val targetFileNameAndFile = sourceFiles + .singleOrNull() + ?.let { fileNameInPackage to it } + ?.also { checkTargetFileName(it.first) } + + val filesExistingInTargetDir = getFilesExistingInTargetDirectory(targetFileNameAndFile?.first, targetDirectory) val moveAsFile = filesExistingInTargetDir.isEmpty() || filesExistingInTargetDir.singleOrNull()?.let { sourceFiles.contains(it) } == true if (!moveAsFile) return null - sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective } + sourceFiles.forEach { it.updatePackageDirective = true } - return if (targetFileName != null) + return if (targetFileNameAndFile != null) MoveToKotlinFileProcessor( project, - sourceFiles.first(), + targetFileNameAndFile.second, targetDirectory, - targetFileName, + targetFileNameAndFile.first, searchInComments = isSearchInComments, searchInNonJavaFiles = isSearchInNonJavaFiles, moveCallback = moveCallback, @@ -291,13 +302,16 @@ internal class MoveKotlinTopLevelDeclarationsModel( private fun moveDeclaration(throwOnConflicts: Boolean): BaseRefactoringProcessor { - val elementsWithMPPIfNeeded = - if (elementsToMoveHasMPP) elementsToMove - .flatMap { it.collectAllExpectAndActualDeclaration() } - .filterIsInstance() - else elementsToMove + if (elementsToMoveHasMPP) require(isMoveToPackage && selectedPsiDirectory == null) + + val target = if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile() + + val elementsWithMPPIfNeeded = if (elementsToMoveHasMPP) + elementsToMove.mapWithReadActionInProcess(project, MoveHandler.REFACTORING_NAME) { + it.collectAllExpectAndActualDeclaration() + }.flatten().filterIsInstance() + else elementsToMove - val target = selectMoveTarget() for (element in elementsWithMPPIfNeeded) { target.verify(element.containingFile)?.let { throw ConfigurationException(it) } } @@ -309,7 +323,7 @@ internal class MoveKotlinTopLevelDeclarationsModel( MoveDeclarationsDelegate.TopLevel, isSearchInComments, isSearchInNonJavaFiles, - deleteSourceFiles = isFullFileMove && isDeleteEmptyFiles, + deleteSourceFiles = isDeleteEmptyFiles, moveCallback = moveCallback, openInEditor = false, allElementsToMove = null, diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt index f0f350e9ac1..3e08b29956a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveUtils.kt @@ -6,7 +6,10 @@ package org.jetbrains.kotlin.idea.refactoring.move import com.intellij.ide.util.DirectoryUtil +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project import com.intellij.openapi.util.Comparing import com.intellij.openapi.util.Key @@ -58,7 +61,6 @@ import org.jetbrains.kotlin.utils.addIfNotNull import java.io.File import java.lang.System.currentTimeMillis import java.util.* -import javax.swing.JCheckBox sealed class ContainerInfo { abstract val fqName: FqName? @@ -708,4 +710,27 @@ internal fun logFusForMoveRefactoring( isSucceeded = succeeded, ) } -} \ No newline at end of file +} + +internal fun List.mapWithReadActionInProcess( + project: Project, + title: String, + body: (KtNamedDeclaration) -> T +): List = let { declarations -> + val result = mutableListOf() + val task: Task.Modal = object : Task.Modal(project, title, false) { + override fun run(indicator: ProgressIndicator) { + val count = 0 + val fraction: Double = 1.0 / declarations.size + indicator.fraction = 0.0 + ApplicationManager.getApplication().runReadAction(Runnable { + for (declaration in declarations) { + result.add(body(declaration)) + indicator.fraction = fraction * count + } + }) + } + } + ProgressManager.getInstance().run(task) + return result +}