Add expect/actual move refactoring support
Fixed #KT-27389
This commit is contained in:
@@ -816,7 +816,7 @@ label.text.target.file.name=Target file name:
|
||||
label.text.to.file=To &file:
|
||||
label.text.to.package=To p&ackage:
|
||||
label.text.type=&Type:\u0020
|
||||
label.text.update.package.directive=&Update package directive
|
||||
label.text.move.expect.actual.counterparts=&Move expect/actual counterparts
|
||||
label.text.visibility=&Visibility:\u0020
|
||||
member.info.abstract.0=abstract {0}
|
||||
member.info.companion.0=companion {0}
|
||||
@@ -876,6 +876,7 @@ text.cannot.inline.0.1.usages=Cannot inline {0}/{1} usages
|
||||
text.cannot.move.for.current.project=Can't move for current project
|
||||
text.cannot.move.inner.class.0.into.itself=Cannot move nested class {0} to itself
|
||||
text.cannot.move.to.original.file=Can't move to the original file
|
||||
text.cannot.move.expect.actual.declaration.to.file=Could't move expect/actual declaration to file
|
||||
text.change.file.package.to.0=Change file''s package to ''{0}''
|
||||
text.choose.containing.file=Choose Containing File
|
||||
text.class.0.already.contains.member.1={0} already contains {1}
|
||||
|
||||
+2
@@ -156,6 +156,7 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep
|
||||
searchInComments: Boolean,
|
||||
searchForTextOccurrences: Boolean,
|
||||
deleteEmptySourceFiles: Boolean,
|
||||
moveMppDeclarations: Boolean,
|
||||
moveCallback: MoveCallback?
|
||||
) {
|
||||
val selectedElementsToMove = mutableSetOf<KtNamedDeclaration>()
|
||||
@@ -181,6 +182,7 @@ internal class MoveKotlinDeclarationsHandlerTestActions(private val caseDataKeep
|
||||
isDeleteEmptyFiles = randomBoolean(),
|
||||
isUpdatePackageDirective = randomBoolean(),
|
||||
isFullFileMove = randomBoolean(),
|
||||
applyMPPDeclarations = true,
|
||||
moveCallback = null
|
||||
)
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ class KotlinRefactoringSettings : PersistentStateComponent<KotlinRefactoringSett
|
||||
@JvmField
|
||||
var MOVE_DELETE_EMPTY_SOURCE_FILES = true
|
||||
|
||||
@JvmField
|
||||
var MOVE_MPP_DECLARATIONS = true
|
||||
|
||||
@JvmField
|
||||
var EXTRACT_INTERFACE_JAVADOC: Int = 0
|
||||
|
||||
|
||||
+1
@@ -160,6 +160,7 @@ class ExtractDeclarationFromCurrentFileIntention :
|
||||
/* searchInComments = */ true,
|
||||
/* searchForTextOccurrences = */ true,
|
||||
/* deleteEmptySourceFiles = */ true,
|
||||
/* moveMppDeclarations = */ false,
|
||||
callBack
|
||||
).showWithTransaction()
|
||||
}
|
||||
|
||||
+4
-13
@@ -71,6 +71,7 @@ private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerAction
|
||||
searchInComments: Boolean,
|
||||
searchForTextOccurrences: Boolean,
|
||||
deleteEmptySourceFiles: Boolean,
|
||||
moveMppDeclarations: Boolean,
|
||||
moveCallback: MoveCallback?
|
||||
) = MoveKotlinTopLevelDeclarationsDialog(
|
||||
project,
|
||||
@@ -82,6 +83,7 @@ private val defaultHandlerActions = object : MoveKotlinDeclarationsHandlerAction
|
||||
searchInComments,
|
||||
searchForTextOccurrences,
|
||||
deleteEmptySourceFiles,
|
||||
moveMppDeclarations,
|
||||
moveCallback
|
||||
).show()
|
||||
|
||||
@@ -127,19 +129,6 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
|
||||
): Boolean {
|
||||
if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, elements.toList(), true)) return false
|
||||
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode &&
|
||||
elements.any { it is KtDeclaration && (it.isExpectDeclaration() || it.isEffectivelyActual()) }
|
||||
) {
|
||||
val proceedWithIncompleteRefactoring = Messages.showYesNoDialog(
|
||||
project,
|
||||
"This refactoring will move selected declaration without it's expect/actual counterparts that may lead to compilation errors.\n" +
|
||||
"Do you wish to proceed?",
|
||||
"MPP declarations does not supported by this refactoring.",
|
||||
Messages.getWarningIcon()
|
||||
)
|
||||
if (proceedWithIncompleteRefactoring != Messages.YES) return true
|
||||
}
|
||||
|
||||
val container = getUniqueContainer(elements)
|
||||
if (container == null) {
|
||||
handlerActions.showErrorHint(
|
||||
@@ -207,6 +196,7 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
|
||||
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
|
||||
|
||||
@@ -220,6 +210,7 @@ class MoveKotlinDeclarationsHandler internal constructor(private val handlerActi
|
||||
searchInComments,
|
||||
searchInText,
|
||||
deleteEmptySourceFiles,
|
||||
moveMppDeclarations,
|
||||
callback
|
||||
)
|
||||
}
|
||||
|
||||
+1
@@ -34,6 +34,7 @@ internal interface MoveKotlinDeclarationsHandlerActions {
|
||||
searchInComments: Boolean,
|
||||
searchForTextOccurrences: Boolean,
|
||||
deleteEmptySourceFiles: Boolean,
|
||||
moveMppDeclarations: Boolean,
|
||||
moveCallback: MoveCallback?
|
||||
)
|
||||
|
||||
|
||||
+10
-1
@@ -116,7 +116,7 @@
|
||||
<text resource-bundle="messages/KotlinBundle" key="label.text.update.package.directive"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="69aba" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="69aba" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="5" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
@@ -160,6 +160,15 @@
|
||||
<text resource-bundle="messages/KotlinBundle" key="checkbox.text.delete.empty.source.files"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="cc60d" class="javax.swing.JCheckBox" binding="cbApplyMPPDeclarationsMove">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="true"/>
|
||||
<text resource-bundle="messages/KotlinBundle" key="label.text.move.expect.actual.counterparts"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
|
||||
+36
-2
@@ -41,6 +41,8 @@ import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTab
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinDestinationFolderComboBox;
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinFileChooserDialog;
|
||||
import org.jetbrains.kotlin.idea.util.ExpectActualUtilKt;
|
||||
import org.jetbrains.kotlin.psi.KtElement;
|
||||
import org.jetbrains.kotlin.psi.KtFile;
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration;
|
||||
import org.jetbrains.kotlin.psi.KtPureElement;
|
||||
@@ -75,6 +77,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
private JCheckBox cbUpdatePackageDirective;
|
||||
private JCheckBox cbDeleteEmptySourceFiles;
|
||||
private JCheckBox cbSearchReferences;
|
||||
private JCheckBox cbApplyMPPDeclarationsMove;
|
||||
private KotlinMemberSelectionTable memberTable;
|
||||
|
||||
private class MemberSelectionerInfoChangeListener implements MemberInfoChangeListener<KtNamedDeclaration, KotlinMemberInfo> {
|
||||
@@ -100,6 +103,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
|
||||
@Override
|
||||
public void memberInfoChanged(@NotNull MemberInfoChange<KtNamedDeclaration, KotlinMemberInfo> event) {
|
||||
updateControls();
|
||||
updatePackageDirectiveCheckBox();
|
||||
updateFileNameInPackageField();
|
||||
// Update file name field only if it user hasn't changed it to some non-default value
|
||||
@@ -119,6 +123,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
boolean searchInComments,
|
||||
boolean searchForTextOccurrences,
|
||||
boolean deleteEmptySourceFiles,
|
||||
boolean moveMppDeclarations,
|
||||
@Nullable MoveCallback moveCallback
|
||||
) {
|
||||
super(project, true);
|
||||
@@ -132,7 +137,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
|
||||
setTitle(MoveHandler.REFACTORING_NAME);
|
||||
|
||||
initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles);
|
||||
initSearchOptions(searchInComments, searchForTextOccurrences, deleteEmptySourceFiles, moveMppDeclarations);
|
||||
|
||||
initPackageChooser(targetPackageName, targetDirectory, sourceFiles);
|
||||
|
||||
@@ -190,6 +195,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
selectionPanel.getTable().setMemberInfoModel(memberInfoModel);
|
||||
selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel);
|
||||
selectionPanel.getTable().addMemberInfoChangeListener(new MemberSelectionerInfoChangeListener(memberInfos));
|
||||
cbApplyMPPDeclarationsMove.addChangeListener(e -> updateControls());
|
||||
memberInfoPanel.add(selectionPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@@ -229,10 +235,11 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
cbUpdatePackageDirective.setSelected(arePackagesAndDirectoryMatched(sourceFiles));
|
||||
}
|
||||
|
||||
private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences, boolean deleteEmptySourceFiles) {
|
||||
private void initSearchOptions(boolean searchInComments, boolean searchForTextOccurences, boolean deleteEmptySourceFiles, boolean moveMppDeclarations) {
|
||||
cbSearchInComments.setSelected(searchInComments);
|
||||
cbSearchTextOccurrences.setSelected(searchForTextOccurences);
|
||||
cbDeleteEmptySourceFiles.setSelected(deleteEmptySourceFiles);
|
||||
cbApplyMPPDeclarationsMove.setSelected(moveMppDeclarations);
|
||||
}
|
||||
|
||||
private void initMoveToButtons(boolean moveToPackage) {
|
||||
@@ -327,7 +334,30 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isMppDeclarationSelected() {
|
||||
for(KtNamedDeclaration element : getSelectedElementsToMove()) {
|
||||
if (ExpectActualUtilKt.isEffectivelyActual(element, true) ||
|
||||
ExpectActualUtilKt.isExpectDeclaration(element)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateControls() {
|
||||
|
||||
boolean mppDeclarationSelected = isMppDeclarationSelected();
|
||||
cbApplyMPPDeclarationsMove.setEnabled(mppDeclarationSelected);
|
||||
|
||||
boolean needToMoveMPPDeclarations = mppDeclarationSelected && cbApplyMPPDeclarationsMove.isSelected();
|
||||
|
||||
if (needToMoveMPPDeclarations) {
|
||||
if (!rbMoveToPackage.isSelected()) {
|
||||
rbMoveToPackage.setSelected(true);
|
||||
}
|
||||
}
|
||||
UIUtil.setEnabled(rbMoveToFile, !needToMoveMPPDeclarations, true);
|
||||
|
||||
boolean moveToPackage = rbMoveToPackage.isSelected();
|
||||
classPackageChooser.setEnabled(moveToPackage);
|
||||
updateFileNameInPackageField();
|
||||
@@ -363,6 +393,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
refactoringSettings.MOVE_SEARCH_FOR_TEXT = cbSearchTextOccurrences.isSelected();
|
||||
refactoringSettings.MOVE_DELETE_EMPTY_SOURCE_FILES = cbDeleteEmptySourceFiles.isSelected();
|
||||
refactoringSettings.MOVE_PREVIEW_USAGES = isPreviewUsages();
|
||||
refactoringSettings.MOVE_MPP_DECLARATIONS = cbApplyMPPDeclarationsMove.isSelected();
|
||||
|
||||
RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, getTargetPackage());
|
||||
}
|
||||
@@ -401,6 +432,8 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) destinationFolderCB.getComboBox().getSelectedItem();
|
||||
PsiDirectory selectedPsiDirectory = selectedItem != null ? selectedItem.getDirectory() : initialTargetDirectory;
|
||||
|
||||
boolean mppDeclarationSelected = cbApplyMPPDeclarationsMove.isSelected() && isMppDeclarationSelected();
|
||||
|
||||
return new MoveKotlinTopLevelDeclarationsModel(
|
||||
myProject,
|
||||
getSelectedElementsToMoveChecked(),
|
||||
@@ -415,6 +448,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
cbDeleteEmptySourceFiles.isSelected(),
|
||||
cbUpdatePackageDirective.isSelected(),
|
||||
isFullFileMove(),
|
||||
mppDeclarationSelected,
|
||||
moveCallback
|
||||
);
|
||||
}
|
||||
|
||||
+21
-2
@@ -27,6 +27,9 @@ import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective
|
||||
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.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
@@ -48,6 +51,7 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
val isDeleteEmptyFiles: Boolean,
|
||||
val isUpdatePackageDirective: Boolean,
|
||||
val isFullFileMove: Boolean,
|
||||
val applyMPPDeclarations: Boolean,
|
||||
val moveCallback: MoveCallback?
|
||||
) : Model<BaseRefactoringProcessor> {
|
||||
|
||||
@@ -59,6 +63,8 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
|
||||
private val sourceFiles: Set<KtFile> = elementsToMove.mapTo(mutableSetOf()) { it.containingKtFile }
|
||||
|
||||
private val elementsToMoveHasMPP = applyMPPDeclarations && elementsToMove.any { it.isEffectivelyActual() || it.isExpectDeclaration() }
|
||||
|
||||
private val singleSourceFileMode = sourceFiles.size == 1
|
||||
|
||||
private fun selectPackageBasedDestination(): MoveDestination {
|
||||
@@ -187,7 +193,11 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile()
|
||||
|
||||
private fun verifyBeforeRun() {
|
||||
if (!isMoveToPackage && elementsToMoveHasMPP)
|
||||
throw ConfigurationException(KotlinBundle.message("text.cannot.move.expect.actual.declaration.to.file"))
|
||||
|
||||
if (elementsToMove.isEmpty()) throw ConfigurationException(KotlinBundle.message("text.at.least.one.file.must.be.selected"))
|
||||
|
||||
if (sourceFiles.isEmpty()) throw ConfigurationException("None elements were selected")
|
||||
if (singleSourceFileMode && fileNameInPackage.isBlank()) throw ConfigurationException(KotlinBundle.message("text.file.name.cannot.be.empty"))
|
||||
|
||||
@@ -220,6 +230,8 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
|
||||
private fun tryMoveFile(throwOnConflicts: Boolean): BaseRefactoringProcessor? {
|
||||
|
||||
if (elementsToMoveHasMPP) return null
|
||||
|
||||
val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage
|
||||
if (targetFileName != null) checkTargetFileName(targetFileName)
|
||||
|
||||
@@ -260,14 +272,21 @@ internal class MoveKotlinTopLevelDeclarationsModel(
|
||||
}
|
||||
|
||||
private fun moveDeclaration(throwOnConflicts: Boolean): BaseRefactoringProcessor {
|
||||
|
||||
val elementsWithMPPIfNeeded =
|
||||
if (elementsToMoveHasMPP) elementsToMove
|
||||
.flatMap { it.collectAllExpectAndActualDeclaration() }
|
||||
.filterIsInstance<KtNamedDeclaration>()
|
||||
else elementsToMove
|
||||
|
||||
val target = selectMoveTarget()
|
||||
for (element in elementsToMove) {
|
||||
for (element in elementsWithMPPIfNeeded) {
|
||||
target.verify(element.containingFile)?.let { throw ConfigurationException(it) }
|
||||
}
|
||||
|
||||
val options = MoveDeclarationsDescriptor(
|
||||
project,
|
||||
MoveSource(elementsToMove),
|
||||
MoveSource(elementsWithMPPIfNeeded),
|
||||
target,
|
||||
MoveDeclarationsDelegate.TopLevel,
|
||||
isSearchInComments,
|
||||
|
||||
Reference in New Issue
Block a user