Move: Implement "Move nested class to upper level" UI
#KT-9027 In Progress
This commit is contained in:
+55
-16
@@ -32,53 +32,92 @@ import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackages
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinNestedClassesToUpperLevelDialog
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinTopLevelDeclarationsDialog
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||
import java.util.*
|
||||
|
||||
class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
private fun getSourceDirectories(elements: Array<out PsiElement>) = elements.mapTo(LinkedHashSet()) { it.containingFile?.parent }
|
||||
private fun getUniqueContainer(elements: Array<out PsiElement>): PsiElement? {
|
||||
val getContainer: (PsiElement) -> PsiElement? =
|
||||
if (elements.any { it.parent !is KtFile }) { e ->
|
||||
(e as? KtNamedDeclaration)?.containingClassOrObject
|
||||
}
|
||||
else { e ->
|
||||
e.containingFile?.parent
|
||||
}
|
||||
return elements.mapNotNullTo(LinkedHashSet(), getContainer).singleOrNull()
|
||||
}
|
||||
|
||||
private fun KtNamedDeclaration.canMove() = if (this is KtClassOrObject) !isLocal() else parent is KtFile
|
||||
|
||||
private fun doMoveWithCheck(
|
||||
project: Project, elements: Array<out PsiElement>, targetContainer: PsiElement?, callback: MoveCallback?, editor: Editor?
|
||||
): Boolean {
|
||||
if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, elements.toList(), true)) return false
|
||||
if (getSourceDirectories(elements).singleOrNull() == null) {
|
||||
|
||||
val container = getUniqueContainer(elements)
|
||||
if (container == null) {
|
||||
CommonRefactoringUtil.showErrorHint(
|
||||
project, editor, "All declarations must belong to the same directory", MOVE_DECLARATIONS, null
|
||||
project, editor, "All declarations must belong to the same directory or class", MOVE_DECLARATIONS, null
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
val elementsToSearch = elements.mapTo(LinkedHashSet()) { it as KtNamedDeclaration }
|
||||
|
||||
if (elementsToSearch.any { it.parent !is KtFile }) {
|
||||
val message = RefactoringBundle.getCannotRefactorMessage("Move declaration is only supported for top-level declarations")
|
||||
// todo: allow moving companion object
|
||||
if (elementsToSearch.any { it is KtObjectDeclaration && it.isCompanion() }) {
|
||||
val message = RefactoringBundle.getCannotRefactorMessage("Move declaration is not supported for companion objects")
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
return true
|
||||
}
|
||||
|
||||
val targetPackageName = MoveClassesOrPackagesImpl.getInitialTargetPackageName(targetContainer, elements)
|
||||
val targetDirectory = MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements)
|
||||
val searchInComments = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_IN_COMMENTS
|
||||
val searchInText = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_FOR_TEXT
|
||||
val targetFile = targetContainer as? KtFile
|
||||
val moveToPackage = targetContainer !is KtFile
|
||||
if (elementsToSearch.any { !it.canMove() }) {
|
||||
val message = RefactoringBundle.getCannotRefactorMessage("Move declaration is only supported for top-level declarations and nested classes")
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, MOVE_DECLARATIONS, null)
|
||||
return true
|
||||
}
|
||||
|
||||
MoveKotlinTopLevelDeclarationsDialog(
|
||||
project, elementsToSearch, targetPackageName, targetDirectory, targetFile, moveToPackage, searchInComments, searchInText, callback
|
||||
).show()
|
||||
when (container) {
|
||||
is PsiDirectory, is PsiPackage, is KtFile -> {
|
||||
val targetPackageName = MoveClassesOrPackagesImpl.getInitialTargetPackageName(targetContainer, elements)
|
||||
val targetDirectory = MoveClassesOrPackagesImpl.getInitialTargetDirectory(targetContainer, elements)
|
||||
val searchInComments = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_IN_COMMENTS
|
||||
val searchInText = JavaRefactoringSettings.getInstance()!!.MOVE_SEARCH_FOR_TEXT
|
||||
val targetFile = targetContainer as? KtFile
|
||||
val moveToPackage = targetContainer !is KtFile
|
||||
|
||||
MoveKotlinTopLevelDeclarationsDialog(
|
||||
project, elementsToSearch, targetPackageName, targetDirectory, targetFile, moveToPackage, searchInComments, searchInText, callback
|
||||
).show()
|
||||
}
|
||||
|
||||
is KtClassOrObject -> {
|
||||
if (elementsToSearch.size > 1) return false
|
||||
|
||||
val outerClass = container.parent as KtClassOrObject
|
||||
val newContainer = targetContainer
|
||||
?: outerClass.containingClassOrObject
|
||||
?: outerClass.containingFile.let { it.containingDirectory ?: it }
|
||||
MoveKotlinNestedClassesToUpperLevelDialog(project, elementsToSearch.first() as KtClassOrObject, newContainer).show()
|
||||
}
|
||||
|
||||
else -> throw AssertionError("Unexpected container: ${container.getElementTextWithContext()}")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun canMove(elements: Array<out PsiElement>, targetContainer: PsiElement?, editorMode: Boolean): Boolean {
|
||||
if (!super.canMove(elements, targetContainer)) return false
|
||||
if (getSourceDirectories(elements).singleOrNull() == null) return false
|
||||
if (getUniqueContainer(elements) == null) return false
|
||||
|
||||
return elements.all { e ->
|
||||
if (e is KtClass || (e is KtObjectDeclaration && !e.isObjectLiteral()) || e is KtNamedFunction || e is KtProperty) {
|
||||
(editorMode || e.parent is KtFile) && e.canRefactor()
|
||||
(editorMode || (e as KtNamedDeclaration).canMove()) && e.canRefactor()
|
||||
}
|
||||
else false
|
||||
}
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui.MoveKotlinNestedClassesToUpperLevelDialog">
|
||||
<grid id="27dc6" binding="panel" layout-manager="GridLayoutManager" row-count="10" 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>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="39dca" class="javax.swing.JLabel" binding="classNameLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/RefactoringBundle" key="class.name.prompt"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="dbb8e">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<vspacer id="34c3">
|
||||
<constraints>
|
||||
<grid row="9" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="8093b" class="com.intellij.ui.EditorTextField" binding="classNameField">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="13f65" class="com.intellij.ui.NonFocusableCheckBox" binding="passOuterClassCheckBox">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/RefactoringBundle" key="pass.outer.class.instance.as.parameter"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="ec0ba" class="javax.swing.JLabel" binding="parameterNameLabel">
|
||||
<constraints>
|
||||
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="1" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/RefactoringBundle" key="parameter.name.prompt"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="73425" class="com.intellij.refactoring.ui.NameSuggestionsField" binding="parameterField" custom-create="true">
|
||||
<constraints>
|
||||
<grid row="6" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="1" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<grid id="60537" layout-manager="FlowLayout" hgap="5" vgap="5" flow-align="0">
|
||||
<constraints>
|
||||
<grid row="7" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="46c54" class="com.intellij.ui.NonFocusableCheckBox" binding="searchInCommentsCheckBox">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<text resource-bundle="messages/RefactoringBundle" key="search.in.comments.and.strings"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="e1ccb" class="com.intellij.ui.NonFocusableCheckBox" binding="searchForTextOccurrencesCheckBox">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<text resource-bundle="messages/RefactoringBundle" key="search.for.text.occurrences"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<component id="ca9da" class="javax.swing.JLabel" binding="packageNameLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Packa&ge name:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="18010" class="com.intellij.refactoring.ui.PackageNameReferenceEditorCombo" binding="packageNameField" custom-create="true">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<grid id="f9ba1" binding="openInEditorPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="8" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.JavaProjectRootsUtil;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.NullableComputable;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.refactoring.HelpID;
|
||||
import com.intellij.refactoring.PackageWrapper;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.refactoring.move.MoveDialogBase;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil;
|
||||
import com.intellij.refactoring.move.moveInner.MoveInnerImpl;
|
||||
import com.intellij.refactoring.ui.NameSuggestionsField;
|
||||
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.refactoring.util.RefactoringMessageUtil;
|
||||
import com.intellij.refactoring.util.RefactoringUtil;
|
||||
import com.intellij.ui.EditorTextField;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType;
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
|
||||
import org.jetbrains.kotlin.idea.core.CollectingNameValidator;
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester;
|
||||
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator;
|
||||
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringUtilKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*;
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.name.Name;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.List;
|
||||
|
||||
public class MoveKotlinNestedClassesToUpperLevelDialog extends MoveDialogBase {
|
||||
@NonNls private static final String RECENTS_KEY = MoveKotlinNestedClassesToUpperLevelDialog.class.getName() + ".RECENTS_KEY";
|
||||
|
||||
private final Project project;
|
||||
private final KtClassOrObject innerClass;
|
||||
private final ClassDescriptor innerClassDescriptor;
|
||||
|
||||
private final PsiElement targetContainer;
|
||||
private EditorTextField classNameField;
|
||||
private NameSuggestionsField parameterField;
|
||||
private JCheckBox passOuterClassCheckBox;
|
||||
private JPanel panel;
|
||||
private JCheckBox searchInCommentsCheckBox;
|
||||
private JCheckBox searchForTextOccurrencesCheckBox;
|
||||
private PackageNameReferenceEditorCombo packageNameField;
|
||||
private JLabel packageNameLabel;
|
||||
private JLabel classNameLabel;
|
||||
private JLabel parameterNameLabel;
|
||||
private JPanel openInEditorPanel;
|
||||
|
||||
public MoveKotlinNestedClassesToUpperLevelDialog(
|
||||
@NotNull Project project,
|
||||
@NotNull KtClassOrObject innerClass,
|
||||
@NotNull PsiElement targetContainer
|
||||
) {
|
||||
super(project, true);
|
||||
this.project = project;
|
||||
this.innerClass = innerClass;
|
||||
this.targetContainer = targetContainer;
|
||||
this.innerClassDescriptor = (ClassDescriptor) ResolutionUtils.resolveToDescriptor(innerClass);
|
||||
setTitle("Move Nested Classes to Upper Level");
|
||||
init();
|
||||
packageNameLabel.setLabelFor(packageNameField.getChildComponent());
|
||||
classNameLabel.setLabelFor(classNameField);
|
||||
parameterNameLabel.setLabelFor(parameterField);
|
||||
openInEditorPanel.add(initOpenInEditorCb(), BorderLayout.EAST);
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
parameterField = new NameSuggestionsField(project);
|
||||
packageNameField = new PackageNameReferenceEditorCombo("", project, RECENTS_KEY,
|
||||
RefactoringBundle.message("choose.destination.package"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMovePropertySuffix() {
|
||||
return "Nested Classes to Upper Level";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHelpId() {
|
||||
return HelpID.MOVE_INNER_UPPER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCbTitle() {
|
||||
return "Open moved member in editor";
|
||||
}
|
||||
|
||||
public boolean isSearchInComments() {
|
||||
return searchInCommentsCheckBox.isSelected();
|
||||
}
|
||||
|
||||
public boolean isSearchInNonJavaFiles() {
|
||||
return searchForTextOccurrencesCheckBox.isSelected();
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return classNameField.getText().trim();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getParameterName() {
|
||||
return parameterField != null ? parameterField.getEnteredName() : null;
|
||||
}
|
||||
|
||||
private boolean isThisNeeded() {
|
||||
return innerClass instanceof KtClass && MoveUtilsKt.traverseOuterInstanceReferences((KtClass) innerClass, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FqName getTargetPackageFqName() {
|
||||
if (targetContainer instanceof PsiDirectory) return innerClass.getContainingKtFile().getPackageFqName();
|
||||
if (targetContainer instanceof KtFile) return ((KtFile) targetContainer).getPackageFqName();
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private KotlinType getOuterInstanceType() {
|
||||
return ((ClassDescriptor) innerClassDescriptor.getContainingDeclaration()).getDefaultType();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
classNameField.setText(innerClass.getName());
|
||||
classNameField.selectAll();
|
||||
|
||||
if (innerClass instanceof KtClass && ((KtClass) innerClass).isInner()) {
|
||||
passOuterClassCheckBox.setSelected(true);
|
||||
passOuterClassCheckBox.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
parameterField.setEnabled(passOuterClassCheckBox.isSelected());
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
passOuterClassCheckBox.setSelected(false);
|
||||
passOuterClassCheckBox.setEnabled(false);
|
||||
parameterField.setEnabled(false);
|
||||
}
|
||||
|
||||
if (passOuterClassCheckBox.isEnabled()) {
|
||||
boolean thisNeeded = isThisNeeded();
|
||||
passOuterClassCheckBox.setSelected(thisNeeded);
|
||||
parameterField.setEnabled(thisNeeded);
|
||||
}
|
||||
|
||||
passOuterClassCheckBox.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
boolean selected = passOuterClassCheckBox.isSelected();
|
||||
parameterField.getComponent().setEnabled(selected);
|
||||
}
|
||||
});
|
||||
|
||||
if (!(targetContainer instanceof PsiDirectory)) {
|
||||
packageNameField.setVisible(false);
|
||||
packageNameLabel.setVisible(false);
|
||||
}
|
||||
|
||||
if (innerClass instanceof KtClass && ((KtClass) innerClass).isInner()) {
|
||||
KtClassBody innerClassBody = innerClass.getBody();
|
||||
Function1<String, Boolean> validator =
|
||||
innerClassBody != null
|
||||
? new NewDeclarationNameValidator(innerClassBody, (PsiElement) null,
|
||||
NewDeclarationNameValidator.Target.VARIABLES)
|
||||
: new CollectingNameValidator();
|
||||
List<String> suggestions = KotlinNameSuggester.INSTANCE.suggestNamesByType(getOuterInstanceType(), validator, "outer");
|
||||
parameterField.setSuggestions(ArrayUtil.toStringArray(suggestions));
|
||||
}
|
||||
else {
|
||||
parameterField.getComponent().setEnabled(false);
|
||||
}
|
||||
|
||||
FqName packageFqName = getTargetPackageFqName();
|
||||
if (packageFqName != null) {
|
||||
packageNameField.prependItem(packageFqName.asString());
|
||||
}
|
||||
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return classNameField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return "#com.intellij.refactoring.move.moveInner.MoveInnerDialog";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createNorthPanel() {
|
||||
return panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement getTargetContainer() {
|
||||
if (targetContainer instanceof PsiDirectory) {
|
||||
PsiDirectory psiDirectory = (PsiDirectory) targetContainer;
|
||||
FqName oldPackageFqName = getTargetPackageFqName();
|
||||
String targetName = packageNameField.getText();
|
||||
if (!Comparing.equal(oldPackageFqName != null ? oldPackageFqName.asString() : null, targetName)) {
|
||||
ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project);
|
||||
List<VirtualFile> contentSourceRoots = JavaProjectRootsUtil.getSuitableDestinationSourceRoots(project);
|
||||
final PackageWrapper newPackage = new PackageWrapper(PsiManager.getInstance(project), targetName);
|
||||
final VirtualFile targetSourceRoot;
|
||||
if (contentSourceRoots.size() > 1) {
|
||||
PsiDirectory initialDir = null;
|
||||
PsiPackage oldPackage = oldPackageFqName != null
|
||||
? JavaPsiFacade.getInstance(project).findPackage(oldPackageFqName.asString())
|
||||
: null;
|
||||
if (oldPackage != null) {
|
||||
PsiDirectory[] directories = oldPackage.getDirectories();
|
||||
VirtualFile root = projectRootManager.getFileIndex().getContentRootForFile(psiDirectory.getVirtualFile());
|
||||
for (PsiDirectory dir : directories) {
|
||||
if (Comparing.equal(projectRootManager.getFileIndex().getContentRootForFile(dir.getVirtualFile()), root)) {
|
||||
initialDir = dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
VirtualFile sourceRoot = MoveClassesOrPackagesUtil.chooseSourceRoot(newPackage, contentSourceRoots, initialDir);
|
||||
if (sourceRoot == null) return null;
|
||||
targetSourceRoot = sourceRoot;
|
||||
}
|
||||
else {
|
||||
targetSourceRoot = contentSourceRoots.get(0);
|
||||
}
|
||||
PsiDirectory dir = RefactoringUtil.findPackageDirectoryInSourceRoot(newPackage, targetSourceRoot);
|
||||
if (dir == null) {
|
||||
dir = ApplicationManager.getApplication().runWriteAction(new NullableComputable<PsiDirectory>() {
|
||||
@Override
|
||||
public PsiDirectory compute() {
|
||||
try {
|
||||
return RefactoringUtil.createPackageDirectoryInSourceRoot(newPackage, targetSourceRoot);
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
return targetContainer;
|
||||
}
|
||||
|
||||
if (targetContainer instanceof KtFile || targetContainer instanceof KtClassOrObject) return targetContainer;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getErrorMessageIfAny() {
|
||||
String className = getClassName();
|
||||
String parameterName = getParameterName();
|
||||
|
||||
if (className != null && className.isEmpty()) return RefactoringBundle.message("no.class.name.specified");
|
||||
if (!KotlinNameSuggester.INSTANCE.isIdentifier(className)) return RefactoringMessageUtil.getIncorrectIdentifierMessage(className);
|
||||
|
||||
if (passOuterClassCheckBox.isSelected()) {
|
||||
if (parameterName != null && parameterName.isEmpty()) return RefactoringBundle.message("no.parameter.name.specified");
|
||||
if (!KotlinNameSuggester.INSTANCE.isIdentifier(parameterName)) return RefactoringMessageUtil.getIncorrectIdentifierMessage(parameterName);
|
||||
}
|
||||
|
||||
PsiElement targetContainer = getTargetContainer();
|
||||
|
||||
if (targetContainer instanceof KtClassOrObject) {
|
||||
KtClassOrObject targetClass = (KtClassOrObject) targetContainer;
|
||||
for (KtDeclaration member : targetClass.getDeclarations()) {
|
||||
if (member instanceof KtClassOrObject && className != null && className.equals(member.getName())) {
|
||||
return RefactoringBundle.message("inner.class.exists", className, targetClass.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetContainer instanceof PsiDirectory || targetContainer instanceof KtFile) {
|
||||
FqName targetPackageFqName = getTargetPackageFqName();
|
||||
if (targetPackageFqName == null) return "No package corresponds to this directory";
|
||||
|
||||
//noinspection ConstantConditions
|
||||
ClassifierDescriptor existingClass = DescriptorUtils
|
||||
.getContainingModule(innerClassDescriptor)
|
||||
.getPackage(targetPackageFqName)
|
||||
.getMemberScope()
|
||||
.getContributedClassifier(Name.identifier(className), NoLookupLocation.FROM_IDE);
|
||||
if (existingClass != null) return "Class " + className + " already exists in package " + targetPackageFqName;
|
||||
|
||||
PsiDirectory targetDir = targetContainer instanceof PsiDirectory
|
||||
? (PsiDirectory) targetContainer
|
||||
: targetContainer.getContainingFile().getContainingDirectory();
|
||||
return RefactoringMessageUtil.checkCanCreateFile(targetDir, className + ".kt");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAction() {
|
||||
String errorMessage = getErrorMessageIfAny();
|
||||
if (errorMessage != null) {
|
||||
CommonRefactoringUtil.showErrorMessage(MoveInnerImpl.REFACTORING_NAME, errorMessage, HelpID.MOVE_INNER_UPPER, project);
|
||||
return;
|
||||
}
|
||||
|
||||
PsiElement target = getTargetContainer();
|
||||
if (target == null) return;
|
||||
|
||||
KotlinMoveTarget moveTarget;
|
||||
if (target instanceof PsiDirectory) {
|
||||
final PsiDirectory targetDir = (PsiDirectory) target;
|
||||
|
||||
final FqName targetPackageFqName = getTargetPackageFqName();
|
||||
if (targetPackageFqName == null) return;
|
||||
|
||||
final String targetFileName = KotlinNameSuggester.INSTANCE.suggestNameByName(
|
||||
innerClass.getName() + "." + KotlinFileType.EXTENSION,
|
||||
new Function1<String, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(String s) {
|
||||
return targetDir.findFile(s) == null;
|
||||
}
|
||||
}
|
||||
);
|
||||
moveTarget = new KotlinMoveTargetForDeferredFile(
|
||||
targetPackageFqName,
|
||||
new Function1<KtFile, KtFile>() {
|
||||
@Override
|
||||
public KtFile invoke(@NotNull KtFile originalFile) {
|
||||
return JetRefactoringUtilKt.createKotlinFile(targetFileName, targetDir, targetPackageFqName.asString());
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
//noinspection ConstantConditions
|
||||
moveTarget = new KotlinMoveTargetForExistingElement((KtElement) target);
|
||||
}
|
||||
|
||||
String outerInstanceParameterName = passOuterClassCheckBox.isSelected() ? getParameterName() : null;
|
||||
String newClassName = getClassName();
|
||||
MoveDeclarationsDelegate delegate = new MoveDeclarationsDelegate.NestedClass(newClassName, outerInstanceParameterName);
|
||||
MoveDeclarationsDescriptor moveDescriptor = new MoveDeclarationsDescriptor(
|
||||
CollectionsKt.listOf(innerClass),
|
||||
moveTarget,
|
||||
delegate,
|
||||
isSearchInComments(),
|
||||
isSearchInNonJavaFiles(),
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
isOpenInEditor()
|
||||
);
|
||||
saveOpenInEditorOption();
|
||||
|
||||
invokeRefactoring(new MoveKotlinDeclarationsProcessor(project, moveDescriptor, Mover.Default.INSTANCE));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user