MPP: Implement directory selection UI supporting non-JVM roots
#KT-27291 Fixed #KT-26696 Fixed
This commit is contained in:
@@ -15,5 +15,9 @@ sealed class KotlinSourceRootType : JpsElementTypeBase<JavaSourceRootProperties>
|
||||
object TestSource : KotlinSourceRootType()
|
||||
|
||||
override fun createDefaultProperties() = JpsJavaExtensionService.getInstance().createSourceRootProperties("")
|
||||
|
||||
companion object {
|
||||
val ALL_SOURCES = setOf(Source, TestSource)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightUtil
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.CreateClassKind
|
||||
import com.intellij.codeInsight.intention.impl.CreateClassDialog
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
@@ -32,14 +31,13 @@ import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.CreateKotlinClassDialog
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory.ClassHeaderBuilder
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.ModifiersChecker
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
private const val IMPL_SUFFIX = "Impl"
|
||||
|
||||
@@ -163,11 +161,11 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention<KtClass>(KtCla
|
||||
PsiElementRenameHandler.rename(klass, project, container, editor)
|
||||
}
|
||||
|
||||
private fun chooseSubclassToCreate(baseClass: KtClass, baseName: String): CreateClassDialog? {
|
||||
private fun chooseSubclassToCreate(baseClass: KtClass, baseName: String): CreateKotlinClassDialog? {
|
||||
val sourceDir = baseClass.containingFile.containingDirectory
|
||||
|
||||
val aPackage = JavaDirectoryService.getInstance().getPackage(sourceDir)
|
||||
val dialog = object : CreateClassDialog(
|
||||
val dialog = object : CreateKotlinClassDialog(
|
||||
baseClass.project, text,
|
||||
targetNameWithoutConflicts(baseName, baseClass.containingClassOrObject),
|
||||
aPackage?.qualifiedName ?: "",
|
||||
|
||||
+2
-1
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.refactoring.SeparateFileWrapper
|
||||
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||
import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary
|
||||
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.CreateKotlinClassDialog
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
@@ -184,7 +185,7 @@ open class CreateClassFromUsageFix<E : KtElement> protected constructor (
|
||||
|
||||
val ideaClassKind = classInfo.kind.toIdeaClassKind()
|
||||
val defaultPackageFqName = file.packageFqName
|
||||
val dialog = object : CreateClassDialog(
|
||||
val dialog = object : CreateKotlinClassDialog(
|
||||
file.project,
|
||||
"Create ${ideaClassKind.description.capitalize()}",
|
||||
className,
|
||||
|
||||
@@ -28,7 +28,6 @@ import com.intellij.refactoring.MoveDestination
|
||||
import com.intellij.refactoring.PackageWrapper
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.DestinationFolderComboBox
|
||||
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.RecentsManager
|
||||
@@ -41,6 +40,7 @@ import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.refactoring.Pass
|
||||
import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinDestinationFolderComboBox
|
||||
import org.jetbrains.kotlin.idea.util.sourceRoot
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
@@ -61,7 +61,7 @@ class CopyKotlinDeclarationDialog(
|
||||
private val packageLabel = JLabel()
|
||||
private lateinit var packageNameField: ReferenceEditorComboWithBrowseButton
|
||||
private val openInEditorCheckBox = CopyFilesOrDirectoriesDialog.createOpenInEditorCB()
|
||||
private val destinationComboBox = object : DestinationFolderComboBox() {
|
||||
private val destinationComboBox = object : KotlinDestinationFolderComboBox() {
|
||||
override fun getTargetPackage() = packageNameField.text.trim()
|
||||
override fun reportBaseInTestSelectionInSource() = true
|
||||
}
|
||||
|
||||
+2
-2
@@ -28,7 +28,6 @@ import com.intellij.refactoring.MoveDestination
|
||||
import com.intellij.refactoring.PackageWrapper
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.DestinationFolderComboBox
|
||||
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.RecentsManager
|
||||
@@ -41,6 +40,7 @@ import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.refactoring.Pass
|
||||
import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinDestinationFolderComboBox
|
||||
import org.jetbrains.kotlin.idea.util.sourceRoot
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
@@ -61,7 +61,7 @@ class CopyKotlinDeclarationDialog(
|
||||
private val packageLabel = JLabel()
|
||||
private lateinit var packageNameField: ReferenceEditorComboWithBrowseButton
|
||||
private val openInEditorCheckBox = CopyFilesOrDirectoriesDialog.createOpenInEditorCB()
|
||||
private val destinationComboBox = object : DestinationFolderComboBox() {
|
||||
private val destinationComboBox = object : KotlinDestinationFolderComboBox() {
|
||||
override fun getTargetPackage() = packageNameField.text.trim()
|
||||
override fun reportBaseInTestSelectionInSource() = true
|
||||
}
|
||||
|
||||
+4
-4
@@ -38,7 +38,6 @@ import com.intellij.refactoring.classMembers.MemberInfoChangeListener;
|
||||
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.DestinationFolderComboBox;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination;
|
||||
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
|
||||
import com.intellij.refactoring.ui.RefactoringDialog;
|
||||
@@ -63,7 +62,7 @@ import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPan
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.MoveUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*;
|
||||
import static org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessorKt.MoveSource;
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinDestinationFolderComboBox;
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinFileChooserDialog;
|
||||
import org.jetbrains.kotlin.idea.util.application.ApplicationUtilsKt;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
@@ -80,6 +79,7 @@ import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessorKt.MoveSource;
|
||||
|
||||
public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
private static final String RECENTS_KEY = "MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY";
|
||||
@@ -300,7 +300,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
classPackageChooser.prependItem(targetPackageName);
|
||||
}
|
||||
|
||||
((DestinationFolderComboBox) destinationFolderCB).setData(
|
||||
((KotlinDestinationFolderComboBox) destinationFolderCB).setData(
|
||||
myProject,
|
||||
targetDirectory,
|
||||
new Pass<String>() {
|
||||
@@ -406,7 +406,7 @@ public class MoveKotlinTopLevelDeclarationsDialog extends RefactoringDialog {
|
||||
private void createUIComponents() {
|
||||
classPackageChooser = createPackageChooser();
|
||||
|
||||
destinationFolderCB = new DestinationFolderComboBox() {
|
||||
destinationFolderCB = new KotlinDestinationFolderComboBox() {
|
||||
@Override
|
||||
public String getTargetPackage() {
|
||||
return MoveKotlinTopLevelDeclarationsDialog.this.getTargetPackage();
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.ui;
|
||||
|
||||
import com.intellij.CommonBundle;
|
||||
import com.intellij.codeInsight.CodeInsightBundle;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.ClassKind;
|
||||
import com.intellij.ide.util.PackageUtil;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CustomShortcutSet;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.JavaProjectRootsUtil;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Pass;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.PsiNameHelper;
|
||||
import com.intellij.refactoring.MoveDestination;
|
||||
import com.intellij.refactoring.PackageWrapper;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination;
|
||||
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
|
||||
import com.intellij.refactoring.util.RefactoringMessageUtil;
|
||||
import com.intellij.ui.DocumentAdapter;
|
||||
import com.intellij.ui.RecentsManager;
|
||||
import com.intellij.ui.ReferenceEditorComboWithBrowseButton;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import java.awt.*;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
// Based on com.intellij.codeInsight.intention.impl.CreateClassDialog
|
||||
public class CreateKotlinClassDialog extends DialogWrapper {
|
||||
private final JLabel myInformationLabel = new JLabel("#");
|
||||
private final JLabel myPackageLabel = new JLabel(CodeInsightBundle.message("dialog.create.class.destination.package.label"));
|
||||
private final ReferenceEditorComboWithBrowseButton myPackageComponent;
|
||||
private final JTextField myTfClassName = new MyTextField();
|
||||
private final Project myProject;
|
||||
private PsiDirectory myTargetDirectory;
|
||||
private final String myClassName;
|
||||
private final boolean myClassNameEditable;
|
||||
private final Module myModule;
|
||||
private final KotlinDestinationFolderComboBox myDestinationCB = new KotlinDestinationFolderComboBox() {
|
||||
@Override
|
||||
public String getTargetPackage() {
|
||||
return myPackageComponent.getText().trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean reportBaseInTestSelectionInSource() {
|
||||
return CreateKotlinClassDialog.this.reportBaseInTestSelectionInSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean reportBaseInSourceSelectionInTest() {
|
||||
return CreateKotlinClassDialog.this.reportBaseInSourceSelectionInTest();
|
||||
}
|
||||
};
|
||||
@NonNls private static final String RECENTS_KEY = "CreateKotlinClassDialog.RecentsKey";
|
||||
|
||||
public CreateKotlinClassDialog(@NotNull Project project,
|
||||
@NotNull String title,
|
||||
@NotNull String targetClassName,
|
||||
@NotNull String targetPackageName,
|
||||
@NotNull ClassKind kind,
|
||||
boolean classNameEditable,
|
||||
@Nullable Module defaultModule) {
|
||||
super(project, true);
|
||||
myClassNameEditable = classNameEditable;
|
||||
myModule = defaultModule;
|
||||
myClassName = targetClassName;
|
||||
myProject = project;
|
||||
myPackageComponent = new PackageNameReferenceEditorCombo(targetPackageName, myProject, RECENTS_KEY, CodeInsightBundle.message("dialog.create.class.package.chooser.title"));
|
||||
myPackageComponent.setTextFieldPreferredWidth(40);
|
||||
|
||||
init();
|
||||
|
||||
if (!myClassNameEditable) {
|
||||
setTitle(CodeInsightBundle.message("dialog.create.class.name", StringUtil.capitalize(kind.getDescription()), targetClassName));
|
||||
}
|
||||
else {
|
||||
myInformationLabel.setText(CodeInsightBundle.message("dialog.create.class.label", kind.getDescription()));
|
||||
setTitle(title);
|
||||
}
|
||||
|
||||
myTfClassName.setText(myClassName);
|
||||
myDestinationCB.setData(myProject, getBaseDir(targetPackageName), new Pass<String>() {
|
||||
@Override
|
||||
public void pass(String s) {
|
||||
setErrorText(s, myDestinationCB);
|
||||
}
|
||||
}, myPackageComponent.getChildComponent());
|
||||
}
|
||||
|
||||
protected boolean reportBaseInTestSelectionInSource() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean reportBaseInSourceSelectionInTest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Action[] createActions() {
|
||||
return new Action[]{getOKAction(), getCancelAction()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myClassNameEditable ? myTfClassName : myPackageComponent.getChildComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return new JPanel(new BorderLayout());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createNorthPanel() {
|
||||
JPanel panel = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints gbConstraints = new GridBagConstraints();
|
||||
|
||||
gbConstraints.insets = JBUI.insets(4, 8);
|
||||
gbConstraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbConstraints.anchor = GridBagConstraints.WEST;
|
||||
|
||||
if (myClassNameEditable) {
|
||||
gbConstraints.weightx = 0;
|
||||
gbConstraints.gridwidth = 1;
|
||||
panel.add(myInformationLabel, gbConstraints);
|
||||
gbConstraints.insets = JBUI.insets(4, 8);
|
||||
gbConstraints.gridx = 1;
|
||||
gbConstraints.weightx = 1;
|
||||
gbConstraints.gridwidth = 1;
|
||||
gbConstraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbConstraints.anchor = GridBagConstraints.WEST;
|
||||
panel.add(myTfClassName, gbConstraints);
|
||||
|
||||
myTfClassName.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
getOKAction().setEnabled(PsiNameHelper.getInstance(myProject).isIdentifier(myTfClassName.getText()));
|
||||
}
|
||||
});
|
||||
getOKAction().setEnabled(StringUtil.isNotEmpty(myClassName));
|
||||
}
|
||||
|
||||
gbConstraints.gridx = 0;
|
||||
gbConstraints.gridy = 2;
|
||||
gbConstraints.weightx = 0;
|
||||
gbConstraints.gridwidth = 1;
|
||||
panel.add(myPackageLabel, gbConstraints);
|
||||
|
||||
gbConstraints.gridx = 1;
|
||||
gbConstraints.weightx = 1;
|
||||
|
||||
new AnAction() {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
myPackageComponent.getButton().doClick();
|
||||
}
|
||||
}.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK)), myPackageComponent.getChildComponent());
|
||||
|
||||
JPanel _panel = new JPanel(new BorderLayout());
|
||||
_panel.add(myPackageComponent, BorderLayout.CENTER);
|
||||
panel.add(_panel, gbConstraints);
|
||||
|
||||
gbConstraints.gridy = 3;
|
||||
gbConstraints.gridx = 0;
|
||||
gbConstraints.gridwidth = 2;
|
||||
gbConstraints.insets.top = 12;
|
||||
gbConstraints.anchor = GridBagConstraints.WEST;
|
||||
gbConstraints.fill = GridBagConstraints.NONE;
|
||||
JBLabel label = new JBLabel(RefactoringBundle.message("target.destination.folder"));
|
||||
panel.add(label, gbConstraints);
|
||||
|
||||
gbConstraints.gridy = 4;
|
||||
gbConstraints.gridx = 0;
|
||||
gbConstraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbConstraints.insets.top = 4;
|
||||
panel.add(myDestinationCB, gbConstraints);
|
||||
|
||||
boolean isMultipleSourceRoots = JavaProjectRootsUtil.getSuitableDestinationSourceRoots(myProject).size() > 1;
|
||||
myDestinationCB.setVisible(isMultipleSourceRoots);
|
||||
label.setVisible(isMultipleSourceRoots);
|
||||
label.setLabelFor(myDestinationCB);
|
||||
return panel;
|
||||
}
|
||||
|
||||
public PsiDirectory getTargetDirectory() {
|
||||
return myTargetDirectory;
|
||||
}
|
||||
|
||||
private String getPackageName() {
|
||||
String name = myPackageComponent.getText();
|
||||
return name != null ? name.trim() : "";
|
||||
}
|
||||
|
||||
private static class MyTextField extends JTextField {
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension size = super.getPreferredSize();
|
||||
FontMetrics fontMetrics = getFontMetrics(getFont());
|
||||
size.width = fontMetrics.charWidth('a') * 40;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, myPackageComponent.getText());
|
||||
String packageName = getPackageName();
|
||||
|
||||
String[] errorString = new String[1];
|
||||
CommandProcessor.getInstance().executeCommand(myProject, () -> {
|
||||
try {
|
||||
PackageWrapper targetPackage = new PackageWrapper(PsiManager.getInstance(myProject), packageName);
|
||||
MoveDestination destination = myDestinationCB.selectDirectory(targetPackage, false);
|
||||
if (destination == null) return;
|
||||
myTargetDirectory = WriteAction.compute(() -> {
|
||||
PsiDirectory baseDir = getBaseDir(packageName);
|
||||
if (baseDir == null && destination instanceof MultipleRootsMoveDestination) {
|
||||
errorString[0] = "Destination not found for package '" + packageName + "'";
|
||||
return null;
|
||||
}
|
||||
return destination.getTargetDirectory(baseDir);
|
||||
});
|
||||
if (myTargetDirectory == null) {
|
||||
return;
|
||||
}
|
||||
errorString[0] = RefactoringMessageUtil.checkCanCreateClass(myTargetDirectory, getClassName());
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
errorString[0] = e.getMessage();
|
||||
}
|
||||
}, CodeInsightBundle.message("create.directory.command"), null);
|
||||
|
||||
if (errorString[0] != null) {
|
||||
if (errorString[0].length() > 0) {
|
||||
Messages.showMessageDialog(myProject, errorString[0], CommonBundle.getErrorTitle(), Messages.getErrorIcon());
|
||||
}
|
||||
return;
|
||||
}
|
||||
super.doOKAction();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected PsiDirectory getBaseDir(String packageName) {
|
||||
return myModule == null? null : PackageUtil.findPossiblePackageDirectoryInModule(myModule, packageName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getClassName() {
|
||||
if (myClassNameEditable) {
|
||||
return myTfClassName.getText();
|
||||
}
|
||||
else {
|
||||
return myClassName;
|
||||
}
|
||||
}
|
||||
}
|
||||
+328
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.ui;
|
||||
|
||||
import com.intellij.ide.util.DirectoryChooser;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.event.DocumentListener;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ProjectFileIndex;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.ui.ComboBoxWithWidePopup;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Pass;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.refactoring.MoveDestination;
|
||||
import com.intellij.refactoring.PackageWrapper;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil;
|
||||
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination;
|
||||
import com.intellij.ui.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt.getKotlinAwareDestinationSourceRoots;
|
||||
|
||||
// Based on com.intellij.refactoring.move.moveClassesOrPackages.DestinationFolderComboBox
|
||||
public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowseButton {
|
||||
private static final String LEAVE_IN_SAME_SOURCE_ROOT = "Leave in same source root";
|
||||
private static final DirectoryChooser.ItemWrapper NULL_WRAPPER = new DirectoryChooser.ItemWrapper(null, null);
|
||||
private PsiDirectory myInitialTargetDirectory;
|
||||
private List<VirtualFile> mySourceRoots;
|
||||
|
||||
public KotlinDestinationFolderComboBox() {
|
||||
super(new ComboBoxWithWidePopup());
|
||||
}
|
||||
|
||||
public abstract String getTargetPackage();
|
||||
|
||||
protected boolean reportBaseInTestSelectionInSource() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean reportBaseInSourceSelectionInTest() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setData(
|
||||
Project project,
|
||||
PsiDirectory initialTargetDirectory,
|
||||
EditorComboBox editorComboBox
|
||||
) {
|
||||
setData(project, initialTargetDirectory, new Pass<String>() {
|
||||
@Override
|
||||
public void pass(String s) {
|
||||
}
|
||||
}, editorComboBox);
|
||||
}
|
||||
|
||||
public void setData(
|
||||
Project project,
|
||||
PsiDirectory initialTargetDirectory,
|
||||
Pass<String> errorMessageUpdater, EditorComboBox editorComboBox
|
||||
) {
|
||||
myInitialTargetDirectory = initialTargetDirectory;
|
||||
mySourceRoots = getKotlinAwareDestinationSourceRoots(project);
|
||||
new ComboboxSpeedSearch(getComboBox()) {
|
||||
@Override
|
||||
protected String getElementText(Object element) {
|
||||
if (element == NULL_WRAPPER) return LEAVE_IN_SAME_SOURCE_ROOT;
|
||||
if (element instanceof DirectoryChooser.ItemWrapper) {
|
||||
VirtualFile virtualFile = ((DirectoryChooser.ItemWrapper) element).getDirectory().getVirtualFile();
|
||||
Module module = ModuleUtilCore.findModuleForFile(virtualFile, project);
|
||||
if (module != null) {
|
||||
return module.getName();
|
||||
}
|
||||
}
|
||||
return super.getElementText(element);
|
||||
}
|
||||
};
|
||||
ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
|
||||
getComboBox().setRenderer(new ListCellRendererWrapper<DirectoryChooser.ItemWrapper>() {
|
||||
@Override
|
||||
public void customize(
|
||||
JList list,
|
||||
DirectoryChooser.ItemWrapper itemWrapper,
|
||||
int index,
|
||||
boolean selected,
|
||||
boolean hasFocus
|
||||
) {
|
||||
if (itemWrapper != NULL_WRAPPER && itemWrapper != null) {
|
||||
setIcon(itemWrapper.getIcon(fileIndex));
|
||||
|
||||
setText(itemWrapper.getRelativeToProjectPath());
|
||||
}
|
||||
else {
|
||||
setText(LEAVE_IN_SAME_SOURCE_ROOT);
|
||||
}
|
||||
}
|
||||
});
|
||||
VirtualFile initialSourceRoot =
|
||||
initialTargetDirectory != null ? fileIndex.getSourceRootForFile(initialTargetDirectory.getVirtualFile()) : null;
|
||||
VirtualFile[] selection = new VirtualFile[] {initialSourceRoot};
|
||||
addActionListener(e -> {
|
||||
VirtualFile root = MoveClassesOrPackagesUtil
|
||||
.chooseSourceRoot(new PackageWrapper(PsiManager.getInstance(project), getTargetPackage()), mySourceRoots,
|
||||
initialTargetDirectory);
|
||||
if (root == null) return;
|
||||
ComboBoxModel model = getComboBox().getModel();
|
||||
for (int i = 0; i < model.getSize(); i++) {
|
||||
DirectoryChooser.ItemWrapper item = (DirectoryChooser.ItemWrapper) model.getElementAt(i);
|
||||
if (item != NULL_WRAPPER &&
|
||||
Comparing.equal(fileIndex.getSourceRootForFile(item.getDirectory().getVirtualFile()), root)) {
|
||||
getComboBox().setSelectedItem(item);
|
||||
getComboBox().repaint();
|
||||
return;
|
||||
}
|
||||
}
|
||||
setComboboxModel(getComboBox(), root, root, fileIndex, mySourceRoots, project, true, errorMessageUpdater);
|
||||
});
|
||||
|
||||
editorComboBox.addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void documentChanged(DocumentEvent e) {
|
||||
JComboBox comboBox = getComboBox();
|
||||
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) comboBox.getSelectedItem();
|
||||
setComboboxModel(comboBox, selectedItem != null && selectedItem != NULL_WRAPPER ? fileIndex
|
||||
.getSourceRootForFile(selectedItem.getDirectory().getVirtualFile()) : initialSourceRoot, selection[0], fileIndex,
|
||||
mySourceRoots, project, false, errorMessageUpdater);
|
||||
}
|
||||
});
|
||||
setComboboxModel(getComboBox(), initialSourceRoot, selection[0], fileIndex, mySourceRoots, project, false, errorMessageUpdater);
|
||||
getComboBox().addActionListener(e -> {
|
||||
Object selectedItem = getComboBox().getSelectedItem();
|
||||
updateErrorMessage(errorMessageUpdater, fileIndex, selectedItem);
|
||||
if (selectedItem instanceof DirectoryChooser.ItemWrapper && selectedItem != NULL_WRAPPER) {
|
||||
PsiDirectory directory = ((DirectoryChooser.ItemWrapper) selectedItem).getDirectory();
|
||||
if (directory != null) {
|
||||
selection[0] = fileIndex.getSourceRootForFile(directory.getVirtualFile());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MoveDestination selectDirectory(PackageWrapper targetPackage, boolean showChooserWhenDefault) {
|
||||
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) getComboBox().getSelectedItem();
|
||||
if (selectedItem == null || selectedItem == NULL_WRAPPER) {
|
||||
return new MultipleRootsMoveDestination(targetPackage);
|
||||
}
|
||||
PsiDirectory selectedPsiDirectory = selectedItem.getDirectory();
|
||||
VirtualFile selectedDestination = selectedPsiDirectory.getVirtualFile();
|
||||
if (showChooserWhenDefault &&
|
||||
myInitialTargetDirectory != null && Comparing.equal(selectedDestination, myInitialTargetDirectory.getVirtualFile()) &&
|
||||
mySourceRoots.size() > 1) {
|
||||
selectedDestination = MoveClassesOrPackagesUtil.chooseSourceRoot(targetPackage, mySourceRoots, myInitialTargetDirectory);
|
||||
}
|
||||
if (selectedDestination == null) return null;
|
||||
return new AutocreatingSingleSourceRootMoveDestination(targetPackage, selectedDestination);
|
||||
}
|
||||
|
||||
private void updateErrorMessage(Pass<String> updateErrorMessage, ProjectFileIndex fileIndex, Object selectedItem) {
|
||||
updateErrorMessage.pass(null);
|
||||
if (myInitialTargetDirectory != null && selectedItem instanceof DirectoryChooser.ItemWrapper && selectedItem != NULL_WRAPPER) {
|
||||
PsiDirectory directory = ((DirectoryChooser.ItemWrapper) selectedItem).getDirectory();
|
||||
boolean isSelectionInTestSourceContent = fileIndex.isInTestSourceContent(directory.getVirtualFile());
|
||||
boolean inTestSourceContent = fileIndex.isInTestSourceContent(myInitialTargetDirectory.getVirtualFile());
|
||||
if (isSelectionInTestSourceContent != inTestSourceContent) {
|
||||
if (inTestSourceContent && reportBaseInTestSelectionInSource()) {
|
||||
updateErrorMessage.pass("Source root is selected while the test root is expected");
|
||||
}
|
||||
|
||||
if (isSelectionInTestSourceContent && reportBaseInSourceSelectionInTest()) {
|
||||
updateErrorMessage.pass("Test root is selected while the source root is expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setComboboxModel(
|
||||
JComboBox comboBox,
|
||||
VirtualFile initialTargetDirectorySourceRoot,
|
||||
VirtualFile oldSelection,
|
||||
ProjectFileIndex fileIndex,
|
||||
List<VirtualFile> sourceRoots,
|
||||
Project project,
|
||||
boolean forceIncludeAll,
|
||||
Pass<String> updateErrorMessage
|
||||
) {
|
||||
LinkedHashSet<PsiDirectory> targetDirectories = new LinkedHashSet<>();
|
||||
HashMap<PsiDirectory, String> pathsToCreate = new HashMap<>();
|
||||
MoveClassesOrPackagesUtil
|
||||
.buildDirectoryList(new PackageWrapper(PsiManager.getInstance(project), getTargetPackage()), sourceRoots, targetDirectories,
|
||||
pathsToCreate);
|
||||
if (!forceIncludeAll && targetDirectories.size() > pathsToCreate.size()) {
|
||||
targetDirectories.removeAll(pathsToCreate.keySet());
|
||||
}
|
||||
ArrayList<DirectoryChooser.ItemWrapper> items = new ArrayList<>();
|
||||
DirectoryChooser.ItemWrapper initial = null;
|
||||
DirectoryChooser.ItemWrapper oldOne = null;
|
||||
for (PsiDirectory targetDirectory : targetDirectories) {
|
||||
DirectoryChooser.ItemWrapper itemWrapper =
|
||||
new DirectoryChooser.ItemWrapper(targetDirectory, pathsToCreate.get(targetDirectory));
|
||||
items.add(itemWrapper);
|
||||
VirtualFile sourceRootForFile = fileIndex.getSourceRootForFile(targetDirectory.getVirtualFile());
|
||||
if (Comparing.equal(sourceRootForFile, initialTargetDirectorySourceRoot)) {
|
||||
initial = itemWrapper;
|
||||
}
|
||||
else if (Comparing.equal(sourceRootForFile, oldSelection)) {
|
||||
oldOne = itemWrapper;
|
||||
}
|
||||
}
|
||||
if (oldSelection == null || !fileIndex.isInLibrarySource(oldSelection)) {
|
||||
items.add(NULL_WRAPPER);
|
||||
}
|
||||
DirectoryChooser.ItemWrapper selection = chooseSelection(initialTargetDirectorySourceRoot, fileIndex, items, initial, oldOne);
|
||||
ComboBoxModel model = comboBox.getModel();
|
||||
if (model instanceof CollectionComboBoxModel) {
|
||||
boolean sameModel = model.getSize() == items.size();
|
||||
if (sameModel) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
DirectoryChooser.ItemWrapper oldItem = (DirectoryChooser.ItemWrapper) model.getElementAt(i);
|
||||
DirectoryChooser.ItemWrapper itemWrapper = items.get(i);
|
||||
if (!areItemsEquivalent(oldItem, itemWrapper)) {
|
||||
sameModel = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sameModel) {
|
||||
if (areItemsEquivalent((DirectoryChooser.ItemWrapper) comboBox.getSelectedItem(), selection)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateErrorMessage(updateErrorMessage, fileIndex, selection);
|
||||
items.sort((o1, o2) -> {
|
||||
if (o1 == NULL_WRAPPER) return -1;
|
||||
if (o2 == NULL_WRAPPER) return 1;
|
||||
return o1.getRelativeToProjectPath().compareToIgnoreCase(o2.getRelativeToProjectPath());
|
||||
});
|
||||
comboBox.setModel(new CollectionComboBoxModel(items, selection));
|
||||
|
||||
Component root = SwingUtilities.getRoot(comboBox);
|
||||
if (root instanceof Window) {
|
||||
Dimension preferredSize = root.getPreferredSize();
|
||||
if (preferredSize.getWidth() > root.getSize().getWidth()) {
|
||||
root.setSize(preferredSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static DirectoryChooser.ItemWrapper chooseSelection(
|
||||
VirtualFile initialTargetDirectorySourceRoot,
|
||||
ProjectFileIndex fileIndex,
|
||||
ArrayList<DirectoryChooser.ItemWrapper> items,
|
||||
DirectoryChooser.ItemWrapper initial,
|
||||
DirectoryChooser.ItemWrapper oldOne
|
||||
) {
|
||||
if (initial != null ||
|
||||
((initialTargetDirectorySourceRoot == null || items.size() > 2) && items.contains(NULL_WRAPPER)) ||
|
||||
items.isEmpty()) {
|
||||
return initial;
|
||||
}
|
||||
else {
|
||||
if (oldOne != null) {
|
||||
return oldOne;
|
||||
}
|
||||
else if (initialTargetDirectorySourceRoot != null) {
|
||||
boolean inTest = fileIndex.isInTestSourceContent(initialTargetDirectorySourceRoot);
|
||||
for (DirectoryChooser.ItemWrapper item : items) {
|
||||
PsiDirectory directory = item.getDirectory();
|
||||
if (directory != null) {
|
||||
VirtualFile virtualFile = directory.getVirtualFile();
|
||||
if (fileIndex.isInTestSourceContent(virtualFile) == inTest) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return items.get(0);
|
||||
}
|
||||
|
||||
private static boolean areItemsEquivalent(DirectoryChooser.ItemWrapper oItem, DirectoryChooser.ItemWrapper itemWrapper) {
|
||||
if (oItem == NULL_WRAPPER || itemWrapper == NULL_WRAPPER) {
|
||||
if (oItem != itemWrapper) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (oItem == null) return itemWrapper == null;
|
||||
if (itemWrapper == null) return false;
|
||||
if (oItem.getDirectory() != itemWrapper.getDirectory()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isAccessible(
|
||||
Project project,
|
||||
VirtualFile virtualFile,
|
||||
VirtualFile targetVirtualFile
|
||||
) {
|
||||
boolean inTestSourceContent = ProjectRootManager.getInstance(project).getFileIndex().isInTestSourceContent(virtualFile);
|
||||
Module module = ModuleUtilCore.findModuleForFile(virtualFile, project);
|
||||
if (targetVirtualFile != null &&
|
||||
module != null &&
|
||||
!GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, inTestSourceContent).contains(targetVirtualFile)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,18 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.roots
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.rootManager
|
||||
import com.intellij.openapi.roots.JavaProjectRootsUtil.isForGeneratedSources
|
||||
import com.intellij.openapi.roots.ModifiableRootModel
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.jps.model.JpsElement
|
||||
import org.jetbrains.jps.model.ex.JpsElementBase
|
||||
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes
|
||||
import org.jetbrains.jps.model.java.JavaResourceRootType
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootProperties
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType
|
||||
import org.jetbrains.jps.model.module.JpsModuleSourceRoot
|
||||
import org.jetbrains.jps.model.module.JpsModuleSourceRootType
|
||||
@@ -41,3 +49,20 @@ fun migrateNonJvmSourceFolders(modifiableRootModel: ModifiableRootModel) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getKotlinAwareDestinationSourceRoots(project: Project): List<VirtualFile> {
|
||||
return ModuleManager.getInstance(project).modules.flatMap { it.collectKotlinAwareDestinationSourceRoots() }
|
||||
}
|
||||
|
||||
private val KOTLIN_AWARE_SOURCE_ROOT_TYPES: Set<JpsModuleSourceRootType<JavaSourceRootProperties>> =
|
||||
JavaModuleSourceRootTypes.SOURCES + KotlinSourceRootType.ALL_SOURCES
|
||||
|
||||
private fun Module.collectKotlinAwareDestinationSourceRoots(): List<VirtualFile> {
|
||||
return rootManager
|
||||
.contentEntries
|
||||
.asSequence()
|
||||
.flatMap { it.getSourceFolders(KOTLIN_AWARE_SOURCE_ROOT_TYPES).asSequence() }
|
||||
.filterNot { isForGeneratedSources(it) }
|
||||
.mapNotNull { it.file }
|
||||
.toList()
|
||||
}
|
||||
Reference in New Issue
Block a user