From a9de0a219b073b0886f5ee361cf46fd9255fcee7 Mon Sep 17 00:00:00 2001 From: Igor Yakovlev Date: Tue, 25 Feb 2020 23:32:41 +0300 Subject: [PATCH] Refactor of Move refactoring 1) Expect/actual move checkbox is visible when expect/actual listed in selection list only 2) Fixed invalid temprary renaming (when source file name exists in target directory) 3) Fixed invalid file delition when "delete all files" checkbox is unchecked (KT-36072) 4) Removed UpdatePackageDirective checkbox (now it always updated instead of unpredicted behavior) 5) Removed SpecifyFileNameInPackage checkbox (in case of it redundancy) 6) Wrap resolve operations into the process 7) Minor refactorings 8) Fixed MPP declaration move top level declarations --- ...oveKotlinDeclarationsHandlerTestActions.kt | 10 +- .../MoveKotlinDeclarationsHandler.kt | 25 +-- .../MoveKotlinDeclarationsHandlerActions.kt | 4 - .../MoveKotlinDeclarationsProcessor.kt | 2 +- .../MoveToKotlinFileProcessor.kt | 15 +- ...oveKotlinNestedClassesToUpperLevelModel.kt | 8 +- .../MoveKotlinTopLevelDeclarationsDialog.form | 28 +--- .../MoveKotlinTopLevelDeclarationsDialog.java | 158 ++++++++---------- .../ui/MoveKotlinTopLevelDeclarationsModel.kt | 116 +++++++------ .../kotlin/idea/refactoring/move/moveUtils.kt | 29 +++- 10 files changed, 190 insertions(+), 205 deletions(-) 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 +}