MPP: Implement directory selection UI supporting non-JVM roots

#KT-27291 Fixed
 #KT-26696 Fixed
This commit is contained in:
Alexey Sedunov
2018-10-16 19:28:49 +03:00
parent aa4e87fc4b
commit 839d936918
9 changed files with 649 additions and 14 deletions
@@ -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 ?: "",
@@ -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
}
@@ -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
}
@@ -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;
}
}
}
@@ -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()
}