Move: make minor refactorings and add GUI form for MoveKotlinMethodDialog
This commit is contained in:
committed by
Igor Yakovlev
parent
5515465760
commit
ed67517fb9
+88
@@ -0,0 +1,88 @@
|
||||
<?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.moveMethod.MoveKotlinMethodDialog">
|
||||
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" 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>
|
||||
<grid id="ea7b1" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" 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>
|
||||
<component id="2ea99" class="javax.swing.JRadioButton" binding="toObjectRadioButton">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/KotlinBundle" key="label.text.to.object"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="eacad" binding="targetObjectChooserPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
<grid id="87410" binding="parametersPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="f056" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" 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>
|
||||
<component id="59af5" class="javax.swing.JRadioButton" binding="toClassRadioButton">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/KotlinBundle" key="label.text.to.class"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="9bb47" binding="targetVariableListPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="2" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<component id="2229f" class="javax.swing.JCheckBox" binding="openInEditorCheckBox">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="true"/>
|
||||
<text resource-bundle="messages/KotlinBundle" key="open.moved.method.in.editor"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<buttonGroups>
|
||||
<group name="group1">
|
||||
<member id="2ea99"/>
|
||||
<member id="59af5"/>
|
||||
</group>
|
||||
</buttonGroups>
|
||||
</form>
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.move.moveMethod;
|
||||
|
||||
import com.intellij.ide.util.TreeClassChooser;
|
||||
import com.intellij.ide.util.TreeJavaClassChooserDialog;
|
||||
import com.intellij.openapi.ui.VerticalFlowLayout;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.refactoring.ui.RefactoringDialog;
|
||||
import com.intellij.ui.EditorTextField;
|
||||
import com.intellij.ui.ScrollPaneFactory;
|
||||
import com.intellij.ui.TitledSeparator;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.asJava.LightClassUtilsKt;
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration;
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle;
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType;
|
||||
import org.jetbrains.kotlin.idea.KotlinIconProvider;
|
||||
import org.jetbrains.kotlin.idea.completion.CompletionUtilsKt;
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester;
|
||||
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator;
|
||||
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject;
|
||||
import org.jetbrains.kotlin.idea.core.completion.PackageLookupObject;
|
||||
import org.jetbrains.kotlin.idea.projectView.KtClassOrObjectTreeNode;
|
||||
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt;
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton;
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jetbrains.kotlin.name.Name.isValidIdentifier;
|
||||
|
||||
public class MoveKotlinMethodDialog extends RefactoringDialog {
|
||||
private static final String RECENTS_KEY = MoveKotlinMethodDialog.class.getName() + ".RECENTS_KEY";
|
||||
private final KtNamedFunction method;
|
||||
private final Map<KtNamedDeclaration, KtClass> variableToClassMap;
|
||||
private final KtNamedDeclaration[] variables;
|
||||
private final Map<KtClass, Set<KtNamedDeclaration>> thisClassesToMembers;
|
||||
private final KtClassOrObject targetContainer;
|
||||
private final Map<KtClass, EditorTextField> oldClassParameterNameFields;
|
||||
private KtNamedDeclaration selectedTarget;
|
||||
private JPanel mainPanel;
|
||||
private JRadioButton toClassRadioButton;
|
||||
private JRadioButton toObjectRadioButton;
|
||||
private JPanel targetObjectChooserPanel;
|
||||
private KotlinTypeReferenceEditorComboWithBrowseButton targetObjectChooser;
|
||||
private JList<KtNamedDeclaration> targetVariableList;
|
||||
private JPanel targetVariableListPanel;
|
||||
private JPanel parametersPanel;
|
||||
private JCheckBox openInEditorCheckBox;
|
||||
|
||||
public MoveKotlinMethodDialog(
|
||||
@NotNull KtNamedFunction method,
|
||||
@NotNull Map<KtNamedDeclaration, KtClass> variableToClassMap,
|
||||
@Nullable KtClassOrObject targetContainer
|
||||
) {
|
||||
super(method.getProject(), true);
|
||||
|
||||
this.method = method;
|
||||
this.variableToClassMap = variableToClassMap;
|
||||
this.targetContainer = targetContainer;
|
||||
this.thisClassesToMembers = MoveKotlinMethodProcessorKt.getThisClassesToMembers(method);
|
||||
this.variables = variableToClassMap.keySet().toArray(new KtNamedDeclaration[0]);
|
||||
this.oldClassParameterNameFields = new HashMap<>();
|
||||
|
||||
init();
|
||||
setTitle(KotlinBundle.message("title.move.method"));
|
||||
initTargetObjectChooser();
|
||||
initTargetVariableList();
|
||||
initParametersPanel();
|
||||
initButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAction() {
|
||||
if (toClassRadioButton.isSelected()) selectedTarget = targetVariableList.getSelectedValue();
|
||||
if (toObjectRadioButton.isSelected() && selectedTarget == null) {
|
||||
setErrorText(KotlinBundle.message("text.no.destination.object.specified"));
|
||||
return;
|
||||
}
|
||||
|
||||
Map<KtClass, String> oldClassParameterNames = new LinkedHashMap<>();
|
||||
for (Map.Entry<KtClass, EditorTextField> entry : oldClassParameterNameFields.entrySet()) {
|
||||
EditorTextField field = entry.getValue();
|
||||
if (!isValidIdentifier(field.getText())) {
|
||||
setErrorText(KotlinBundle.message("parameter.name.is.invalid", field.getText()));
|
||||
return;
|
||||
}
|
||||
if (field.isEnabled()) {
|
||||
oldClassParameterNames.put(entry.getKey(), field.getText());
|
||||
}
|
||||
}
|
||||
|
||||
MoveKotlinMethodProcessor processor =
|
||||
new MoveKotlinMethodProcessor(method, selectedTarget, oldClassParameterNames, openInEditorCheckBox.isSelected());
|
||||
invokeRefactoring(processor);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
private void initButtons() {
|
||||
toClassRadioButton.addActionListener(e -> enableTargetChooser());
|
||||
toObjectRadioButton.addActionListener(e -> enableTargetChooser());
|
||||
|
||||
if (variables.length != 0 && !(targetContainer instanceof KtObjectDeclaration)) {
|
||||
toClassRadioButton.setSelected(true);
|
||||
}
|
||||
else {
|
||||
toObjectRadioButton.setSelected(true);
|
||||
if (variables.length == 0) {
|
||||
toClassRadioButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
enableTextFields();
|
||||
enableTargetChooser();
|
||||
}
|
||||
|
||||
private void initTargetVariableList() {
|
||||
AbstractListModel<KtNamedDeclaration> listModel = new AbstractListModel<KtNamedDeclaration>() {
|
||||
@Override
|
||||
public int getSize() {
|
||||
return variables.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KtNamedDeclaration getElementAt(int index) {
|
||||
return variables[index];
|
||||
}
|
||||
};
|
||||
targetVariableList = new JBList<>(listModel);
|
||||
DefaultListCellRenderer listCellRenderer = new DefaultListCellRenderer() {
|
||||
private final DescriptorRenderer renderer = IdeDescriptorRenderers.SOURCE_CODE_TYPES_WITH_SHORT_NAMES;
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(
|
||||
JList<?> list,
|
||||
Object value,
|
||||
int index,
|
||||
boolean isSelected,
|
||||
boolean cellHasFocus
|
||||
) {
|
||||
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
if (value instanceof KtNamedDeclaration) {
|
||||
KtNamedDeclaration variable = (KtNamedDeclaration) value;
|
||||
setIcon(KotlinIconProvider.Companion.getBaseIcon(variable));
|
||||
setText(variable.getName());
|
||||
KotlinType type = MoveKotlinMethodProcessorKt.type(variable);
|
||||
if (type != null) {
|
||||
setText(getText() + ": " + renderer.renderType(type));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
targetVariableList.setCellRenderer(listCellRenderer);
|
||||
targetVariableList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
||||
int defaultVariableIndex = -1;
|
||||
for (int i = 0; i < variables.length; i++) {
|
||||
if (variableToClassMap.get(variables[i]) == targetContainer) {
|
||||
defaultVariableIndex = i;
|
||||
}
|
||||
}
|
||||
targetVariableList.setSelectedIndex(defaultVariableIndex != -1 ? defaultVariableIndex : 0);
|
||||
targetVariableList.getSelectionModel().addListSelectionListener(e -> enableTextFields());
|
||||
|
||||
JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(targetVariableList);
|
||||
targetVariableListPanel.add(scrollPane);
|
||||
}
|
||||
|
||||
private void initTargetObjectChooser() {
|
||||
targetObjectChooser = new KotlinTypeReferenceEditorComboWithBrowseButton(
|
||||
e -> {
|
||||
TreeClassChooser chooser = createTreeClassChooser();
|
||||
chooser.selectDirectory(method.getContainingFile().getContainingDirectory());
|
||||
chooser.showDialog();
|
||||
PsiClass psiClass = chooser.getSelected();
|
||||
if (psiClass instanceof KtLightClassForSourceDeclaration) {
|
||||
selectedTarget = ((KtLightClassForSourceDeclaration) psiClass).getKotlinOrigin();
|
||||
targetObjectChooser.setText(Objects.requireNonNull(psiClass.getQualifiedName()));
|
||||
}
|
||||
},
|
||||
(targetContainer == null || targetContainer.getFqName() == null) ? null : targetContainer.getFqName().asString(),
|
||||
targetContainer == null ? Objects.requireNonNull(KtPsiUtilKt.getContainingClassOrObject(method)) : targetContainer,
|
||||
RECENTS_KEY
|
||||
);
|
||||
KtTypeCodeFragment codeFragment = targetObjectChooser.getCodeFragment();
|
||||
if (codeFragment != null) {
|
||||
CompletionUtilsKt.setExtraCompletionFilter(
|
||||
codeFragment,
|
||||
lookupElement -> {
|
||||
Object lookupObject = lookupElement.getObject();
|
||||
if (!(lookupObject instanceof DeclarationLookupObject)) return false;
|
||||
PsiElement psiElement = ((DeclarationLookupObject) lookupObject).getPsiElement();
|
||||
if (lookupObject instanceof PackageLookupObject) return true;
|
||||
return (psiElement instanceof KtObjectDeclaration) && KotlinRefactoringUtilKt.canRefactor(psiElement);
|
||||
}
|
||||
);
|
||||
}
|
||||
targetObjectChooserPanel.add(targetObjectChooser);
|
||||
}
|
||||
|
||||
private void enableTextFields() {
|
||||
for (EditorTextField textField : oldClassParameterNameFields.values()) {
|
||||
textField.setEnabled(true);
|
||||
}
|
||||
if (toClassRadioButton.isSelected()) {
|
||||
KtNamedDeclaration variable = variables[targetVariableList.getSelectedIndex()];
|
||||
KtClassOrObject containingClass = KtPsiUtilKt.getContainingClassOrObject(variable);
|
||||
if (!(containingClass instanceof KtClass)) return;
|
||||
if (!(variable instanceof KtParameter) || ((KtParameter) variable).hasValOrVar()) {
|
||||
Set<KtNamedDeclaration> members = thisClassesToMembers.get(containingClass);
|
||||
if (members != null && members.size() == 1 && members.contains(variable)) {
|
||||
EditorTextField field = oldClassParameterNameFields.get(containingClass);
|
||||
if (field != null) field.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void enableTargetChooser() {
|
||||
if (toClassRadioButton.isSelected()) {
|
||||
targetVariableList.setEnabled(true);
|
||||
targetObjectChooser.setEnabled(false);
|
||||
}
|
||||
else {
|
||||
targetVariableList.setEnabled(false);
|
||||
targetObjectChooser.setEnabled(true);
|
||||
}
|
||||
enableTextFields();
|
||||
}
|
||||
|
||||
private void initParametersPanel() {
|
||||
if (thisClassesToMembers.isEmpty()) return;
|
||||
parametersPanel.setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, true));
|
||||
NewDeclarationNameValidator validator =
|
||||
new NewDeclarationNameValidator((PsiElement) method, null, NewDeclarationNameValidator.Target.VARIABLES, new ArrayList<>());
|
||||
|
||||
for (KtClass ktClass : thisClassesToMembers.keySet()) {
|
||||
KotlinType type = MoveKotlinMethodProcessorKt.defaultType(ktClass);
|
||||
if (type == null) continue;
|
||||
String text = KotlinBundle.message("text.select.a.name.for.this.parameter", ktClass.getName());
|
||||
parametersPanel.add(new TitledSeparator(text, null));
|
||||
List<String> suggestedNames = KotlinNameSuggester.INSTANCE.suggestNamesByType(type, validator, null);
|
||||
String suggestedName = suggestedNames.isEmpty() ? "parameter" : suggestedNames.get(0);
|
||||
EditorTextField field = new EditorTextField(suggestedName, myProject, KotlinFileType.INSTANCE);
|
||||
oldClassParameterNameFields.put(ktClass, field);
|
||||
parametersPanel.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
private TreeJavaClassChooserDialog createTreeClassChooser() {
|
||||
return new TreeJavaClassChooserDialog(
|
||||
KotlinBundle.message("title.choose.destination.object"),
|
||||
myProject,
|
||||
GlobalSearchScope.projectScope(myProject),
|
||||
aClass -> {
|
||||
if (!(aClass instanceof KtLightClassForSourceDeclaration)) return false;
|
||||
KtClassOrObject ktClassOrObject = ((KtLightClassForSourceDeclaration) aClass).getKotlinOrigin();
|
||||
return ktClassOrObject instanceof KtObjectDeclaration && !((KtObjectDeclaration) ktClassOrObject).isObjectLiteral();
|
||||
},
|
||||
null,
|
||||
null,
|
||||
true
|
||||
) {
|
||||
@Nullable
|
||||
@Override
|
||||
protected PsiClass getSelectedFromTreeUserObject(DefaultMutableTreeNode node) {
|
||||
PsiClass psiClass = super.getSelectedFromTreeUserObject(node);
|
||||
if (psiClass != null) return psiClass;
|
||||
Object userObject = node.getUserObject();
|
||||
if (!(userObject instanceof KtClassOrObjectTreeNode)) return null;
|
||||
return LightClassUtilsKt.toLightClass(((KtClassOrObjectTreeNode) userObject).getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
-290
@@ -1,290 +0,0 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.move.moveMethod
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.ide.util.TreeClassChooser
|
||||
import com.intellij.ide.util.TreeJavaClassChooserDialog
|
||||
import com.intellij.openapi.ui.VerticalFlowLayout
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.refactoring.ui.RefactoringDialog
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.ScrollPaneFactory
|
||||
import com.intellij.ui.TitledSeparator
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.rd.util.firstOrNull
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinIconProvider.Companion.getBaseIcon
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.completion.extraCompletionFilter
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester.suggestNamesByType
|
||||
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
|
||||
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
|
||||
import org.jetbrains.kotlin.idea.core.completion.PackageLookupObject
|
||||
import org.jetbrains.kotlin.idea.projectView.KtClassOrObjectTreeNode
|
||||
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.name.Name.isValidIdentifier
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy
|
||||
import java.awt.Component
|
||||
import java.awt.Dimension
|
||||
import java.awt.GridBagConstraints
|
||||
import java.awt.GridBagLayout
|
||||
import java.awt.event.ActionListener
|
||||
import javax.swing.*
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
|
||||
|
||||
class MoveKotlinMethodDialog(
|
||||
private val method: KtNamedFunction,
|
||||
private val variableToClassMap: Map<KtNamedDeclaration, KtClass>,
|
||||
private val targetContainer: KtClassOrObject?
|
||||
) : RefactoringDialog(method.project, true) {
|
||||
private val variables = variableToClassMap.keys.toList()
|
||||
private val targetVariableList = createTargetVariableChooser()
|
||||
private val thisClassesToMembers = getThisClassesToMembers(method)
|
||||
private val oldClassParameterNameFields = LinkedHashMap<KtClass, EditorTextField>()
|
||||
private val targetObjectChooser = createTargetObjectChooser()
|
||||
private val toClassRadioButton = JRadioButton(KotlinBundle.message("label.text.to.class"))
|
||||
private val toObjectRadioButton = JRadioButton(KotlinBundle.message("label.text.to.object"))
|
||||
private var selectedTarget: KtNamedDeclaration? = null
|
||||
private val openInEditorCheckBox = JCheckBox(KotlinBundle.message("open.moved.method.in.editor"), true)
|
||||
|
||||
init {
|
||||
super.init()
|
||||
title = KotlinBundle.message("title.move.method")
|
||||
}
|
||||
|
||||
override fun doAction() {
|
||||
if (toClassRadioButton.isSelected) selectedTarget = targetVariableList.selectedValue
|
||||
|
||||
if (toObjectRadioButton.isSelected && selectedTarget == null) {
|
||||
setErrorText(KotlinBundle.message("text.no.destination.object.specified"))
|
||||
return
|
||||
}
|
||||
|
||||
val oldClassParameterNames = LinkedHashMap<KtClass, String>()
|
||||
for ((ktClass, field) in oldClassParameterNameFields) {
|
||||
if (!isValidIdentifier(field.text)) {
|
||||
setErrorText(KotlinBundle.message("parameter.name.is.invalid", field.text))
|
||||
return
|
||||
}
|
||||
if (field.isEnabled) {
|
||||
oldClassParameterNames[ktClass] = field.text
|
||||
}
|
||||
}
|
||||
|
||||
val processor = MoveKotlinMethodProcessor(method, selectedTarget!!, oldClassParameterNames, openInEditorCheckBox.isSelected)
|
||||
invokeRefactoring(processor)
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent? {
|
||||
val mainPanel = JPanel(GridBagLayout())
|
||||
val buttonGroup = ButtonGroup()
|
||||
|
||||
toClassRadioButton.addActionListener { enableTargetChooser() }
|
||||
toObjectRadioButton.addActionListener { enableTargetChooser() }
|
||||
buttonGroup.add(toClassRadioButton)
|
||||
buttonGroup.add(toObjectRadioButton)
|
||||
|
||||
targetVariableList.addListSelectionListener { enableTextFields() }
|
||||
val scrollPane = ScrollPaneFactory.createScrollPane(targetVariableList)
|
||||
mainPanel.add(
|
||||
scrollPane, GridBagConstraints(
|
||||
1, 0, 1, 1, 1.0, 1.0,
|
||||
GridBagConstraints.WEST, GridBagConstraints.BOTH, JBUI.emptyInsets(), 0, 0
|
||||
)
|
||||
)
|
||||
|
||||
mainPanel.add(
|
||||
targetObjectChooser, GridBagConstraints(
|
||||
1, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0,
|
||||
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, JBUI.emptyInsets(), 0, 0
|
||||
)
|
||||
)
|
||||
mainPanel.add(
|
||||
toClassRadioButton, GridBagConstraints(
|
||||
0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0,
|
||||
GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, JBUI.emptyInsets(), 0, 0
|
||||
)
|
||||
)
|
||||
mainPanel.add(
|
||||
toObjectRadioButton, GridBagConstraints(
|
||||
0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0,
|
||||
GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, JBUI.emptyInsets(), 0, 0
|
||||
)
|
||||
)
|
||||
|
||||
val parametersPanel: JPanel? = createParametersPanel()
|
||||
if (parametersPanel != null) {
|
||||
mainPanel.add(
|
||||
parametersPanel, GridBagConstraints(
|
||||
0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0,
|
||||
GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, JBUI.emptyInsets(), 0, 0
|
||||
)
|
||||
)
|
||||
}
|
||||
mainPanel.add(
|
||||
openInEditorCheckBox, GridBagConstraints(
|
||||
0, GridBagConstraints.RELATIVE, 2, 1, 0.0, 0.0,
|
||||
GridBagConstraints.WEST, GridBagConstraints.NONE, JBUI.emptyInsets(), 0, 0
|
||||
)
|
||||
)
|
||||
|
||||
if (variables.isNotEmpty() && targetContainer !is KtObjectDeclaration) {
|
||||
toClassRadioButton.isSelected = true
|
||||
} else {
|
||||
toObjectRadioButton.isSelected = true
|
||||
if (variables.isEmpty()) {
|
||||
toClassRadioButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
enableTextFields()
|
||||
enableTargetChooser()
|
||||
updateOnChanged(targetVariableList)
|
||||
return mainPanel
|
||||
}
|
||||
|
||||
private fun createTargetVariableChooser(): JList<KtNamedDeclaration> {
|
||||
val listModel = object : AbstractListModel<KtNamedDeclaration>() {
|
||||
override fun getElementAt(index: Int): KtNamedDeclaration = variables[index]
|
||||
override fun getSize(): Int = variables.size
|
||||
}
|
||||
val list = JBList(listModel)
|
||||
val listCellRenderer = object : DefaultListCellRenderer() {
|
||||
private val renderer = IdeDescriptorRenderers.SOURCE_CODE_TYPES.withOptions {
|
||||
classifierNamePolicy = ClassifierNamePolicy.SHORT
|
||||
}
|
||||
|
||||
override fun getListCellRendererComponent(
|
||||
list: JList<*>?, value: Any?, index: Int, isSelected: Boolean, cellHasFocus: Boolean
|
||||
): Component {
|
||||
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
|
||||
if (value is KtNamedDeclaration) {
|
||||
icon = value.getBaseIcon()
|
||||
text = value.nameAsSafeName.identifier
|
||||
(value.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType?.let { type ->
|
||||
text = "$text: ${renderer.renderType(type)}"
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
list.cellRenderer = listCellRenderer
|
||||
list.selectionMode = ListSelectionModel.SINGLE_SELECTION
|
||||
val defaultVariableIndex = variables.indexOf(variableToClassMap.filter { it.value == targetContainer }.firstOrNull()?.key)
|
||||
list.selectedIndex = if (defaultVariableIndex != -1) defaultVariableIndex else 0
|
||||
list.selectionModel.addListSelectionListener { updateOnChanged(list) }
|
||||
return list
|
||||
}
|
||||
|
||||
private fun createTargetObjectChooser(): KotlinTypeReferenceEditorComboWithBrowseButton {
|
||||
val targetObjectChooser = KotlinTypeReferenceEditorComboWithBrowseButton(
|
||||
ActionListener {
|
||||
val chooser: TreeClassChooser = object : TreeJavaClassChooserDialog(
|
||||
KotlinBundle.message("title.choose.destination.object"),
|
||||
project,
|
||||
GlobalSearchScope.projectScope(project),
|
||||
{ psiClass ->
|
||||
if (psiClass !is KtLightClassForSourceDeclaration) {
|
||||
false
|
||||
} else {
|
||||
val ktClassOrObject = psiClass.kotlinOrigin
|
||||
ktClassOrObject is KtObjectDeclaration && !ktClassOrObject.isObjectLiteral()
|
||||
}
|
||||
},
|
||||
null,
|
||||
null,
|
||||
true
|
||||
) {
|
||||
override fun getSelectedFromTreeUserObject(node: DefaultMutableTreeNode): PsiClass? {
|
||||
val psiClass = super.getSelectedFromTreeUserObject(node)
|
||||
if (psiClass != null) return psiClass
|
||||
val userObject = node.userObject
|
||||
return if (userObject !is KtClassOrObjectTreeNode) null else userObject.value.toLightClass()
|
||||
}
|
||||
}
|
||||
chooser.selectDirectory(method.containingFile.containingDirectory)
|
||||
chooser.showDialog()
|
||||
val psiClass = chooser.selected
|
||||
if (psiClass is KtLightClassForSourceDeclaration) {
|
||||
selectedTarget = psiClass.kotlinOrigin
|
||||
targetObjectChooser.text = psiClass.kotlinOrigin.fqName.toString()
|
||||
}
|
||||
},
|
||||
(targetContainer ?: method.containingClassOrObject as? KtObjectDeclaration)?.fqName?.asString(),
|
||||
targetContainer ?: method.containingClassOrObject!!,
|
||||
RECENTS_KEY
|
||||
)
|
||||
targetObjectChooser.codeFragment?.let { codeFragment ->
|
||||
codeFragment.extraCompletionFilter = { lookupElement: LookupElement ->
|
||||
val lookupObject = lookupElement.getObject() as? DeclarationLookupObject
|
||||
val psiElement = lookupObject?.psiElement
|
||||
lookupObject is PackageLookupObject || psiElement is KtObjectDeclaration && psiElement.canRefactor()
|
||||
}
|
||||
}
|
||||
return targetObjectChooser
|
||||
}
|
||||
|
||||
private fun updateOnChanged(list: JList<*>) {
|
||||
okAction.isEnabled = !list.selectionModel.isSelectionEmpty
|
||||
}
|
||||
|
||||
private fun enableTextFields() {
|
||||
for (textField in oldClassParameterNameFields.values) {
|
||||
textField.isEnabled = true
|
||||
}
|
||||
if (toClassRadioButton.isSelected) {
|
||||
val variable = variables[targetVariableList.selectedIndex]
|
||||
val containingClass = variable.containingClassOrObject as? KtClass ?: return
|
||||
if (variable !is KtParameter || variable.hasValOrVar()) {
|
||||
if (thisClassesToMembers[containingClass]?.size == 1
|
||||
&& thisClassesToMembers[containingClass]?.contains(variable) == true
|
||||
) {
|
||||
oldClassParameterNameFields[containingClass]?.isEnabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableTargetChooser() {
|
||||
if (toClassRadioButton.isSelected) {
|
||||
targetVariableList.isEnabled = true
|
||||
targetObjectChooser.isEnabled = false
|
||||
} else {
|
||||
targetVariableList.isEnabled = false
|
||||
targetObjectChooser.isEnabled = true
|
||||
}
|
||||
enableTextFields()
|
||||
}
|
||||
|
||||
private fun createParametersPanel(): JPanel? {
|
||||
if (thisClassesToMembers.isEmpty()) return null
|
||||
val panel = JPanel(VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, true))
|
||||
val validator = NewDeclarationNameValidator(method, null, NewDeclarationNameValidator.Target.VARIABLES)
|
||||
for (ktClass in thisClassesToMembers.keys) {
|
||||
val text = KotlinBundle.message("text.select.a.name.for.this.parameter", ktClass.nameAsSafeName.identifier)
|
||||
panel.add(TitledSeparator(text, null))
|
||||
val suggestedName = suggestNamesByType(ktClass.resolveToDescriptorIfAny()!!.defaultType, validator).firstOrNull() ?: "parameter"
|
||||
val field = EditorTextField(suggestedName, project, KotlinFileType.INSTANCE)
|
||||
field.minimumSize = Dimension(field.preferredSize)
|
||||
oldClassParameterNameFields[ktClass] = field
|
||||
panel.add(field)
|
||||
}
|
||||
panel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
|
||||
return panel
|
||||
}
|
||||
|
||||
companion object {
|
||||
val RECENTS_KEY = "${MoveKotlinMethodDialog::class.java.name}.RECENTS_KEY"
|
||||
}
|
||||
}
|
||||
+25
-19
@@ -9,9 +9,7 @@ import com.intellij.psi.PsiReference
|
||||
import com.intellij.refactoring.move.MoveCallback
|
||||
import com.intellij.refactoring.move.MoveHandlerDelegate
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
@@ -22,27 +20,38 @@ import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
|
||||
|
||||
class MoveKotlinMethodHandler : MoveHandlerDelegate() {
|
||||
private fun showErrorHint(project: Project, dataContext: DataContext?, message: String) {
|
||||
val editor = if (dataContext == null) null else CommonDataKeys.EDITOR.getData(dataContext)
|
||||
val editor = dataContext?.let { CommonDataKeys.EDITOR.getData(it) }
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, KotlinBundle.message("text.move.method"), null)
|
||||
}
|
||||
|
||||
private fun invokeMoveMethodRefactoring(
|
||||
project: Project, method: KtNamedFunction, targetContainer: KtClassOrObject?, dataContext: DataContext?
|
||||
project: Project,
|
||||
method: KtNamedFunction,
|
||||
targetContainer: KtClassOrObject?,
|
||||
dataContext: DataContext?
|
||||
) {
|
||||
var message: String? = null
|
||||
if (method.containingClassOrObject == null) return
|
||||
if (!method.manager.isInProject(method)) {
|
||||
message = KotlinBundle.message("text.move.method.is.not.supported.for.non.project.methods")
|
||||
} else if (method.mentionsTypeParameters()) {
|
||||
message = KotlinBundle.message("text.move.method.is.not.supported.for.generic.classes")
|
||||
} else if (method.hasModifier(KtTokens.OVERRIDE_KEYWORD) || method.hasModifier(KtTokens.OPEN_KEYWORD)) {
|
||||
message = KotlinBundle.message("text.move.method.is.not.supported.when.method.is.a.part.of.inheritance.hierarchy")
|
||||
|
||||
val errorMessageKey = when {
|
||||
!method.manager.isInProject(method) ->
|
||||
"text.move.method.is.not.supported.for.non.project.methods"
|
||||
method.mentionsTypeParameters() ->
|
||||
"text.move.method.is.not.supported.for.generic.classes"
|
||||
method.hasModifier(KtTokens.OVERRIDE_KEYWORD) || method.hasModifier(KtTokens.OPEN_KEYWORD) ->
|
||||
"text.move.method.is.not.supported.when.method.is.a.part.of.inheritance.hierarchy"
|
||||
else -> null
|
||||
}
|
||||
message?.let {
|
||||
showErrorHint(project, dataContext, message)
|
||||
|
||||
if (errorMessageKey != null) {
|
||||
showErrorHint(project, dataContext, KotlinBundle.message(errorMessageKey))
|
||||
return
|
||||
}
|
||||
MoveKotlinMethodDialog(method, collectSuitableVariables(method), targetContainer).show()
|
||||
|
||||
MoveKotlinMethodDialog(
|
||||
method,
|
||||
collectSuitableVariables(method),
|
||||
targetContainer
|
||||
).show()
|
||||
}
|
||||
|
||||
private fun collectSuitableVariables(method: KtNamedFunction): Map<KtNamedDeclaration, KtClass> {
|
||||
@@ -55,7 +64,7 @@ class MoveKotlinMethodHandler : MoveHandlerDelegate() {
|
||||
|
||||
val variableToClassMap = LinkedHashMap<KtNamedDeclaration, KtClass>()
|
||||
for (variable in allVariables) {
|
||||
(variable.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType?.let { type ->
|
||||
variable.type()?.let { type ->
|
||||
if (type.arguments.isEmpty()) {
|
||||
val ktClass = type.constructor.declarationDescriptor?.findPsi() as? KtClass
|
||||
if (ktClass != null && method.manager.isInProject(ktClass)) {
|
||||
@@ -108,10 +117,7 @@ class MoveKotlinMethodHandler : MoveHandlerDelegate() {
|
||||
typeParameters.addAll(ktClassOrObject.typeParameters)
|
||||
ktClassOrObject = if (ktClassOrObject.hasModifier(KtTokens.INNER_KEYWORD)) ktClassOrObject.containingClassOrObject else null
|
||||
}
|
||||
collectDescendantsOfType<KtUserType>().forEach { userType ->
|
||||
if (userType.referenceExpression?.mainReference?.resolve() in typeParameters) return true
|
||||
}
|
||||
return false
|
||||
return collectDescendantsOfType<KtUserType>().any { userType -> userType.referenceExpression?.mainReference?.resolve() in typeParameters }
|
||||
}
|
||||
|
||||
override fun getActionName(elements: Array<out PsiElement>): String = "${KotlinBundle.message("text.move.method")}.."
|
||||
|
||||
+7
-3
@@ -283,12 +283,16 @@ class MoveKotlinMethodProcessor(
|
||||
|
||||
internal fun getThisClassesToMembers(method: KtNamedFunction) = traverseOuterInstanceReferences(method)
|
||||
|
||||
internal fun KtNamedDeclaration.type() = (resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType
|
||||
|
||||
internal fun KtClass.defaultType() = resolveToDescriptorIfAny()?.defaultType
|
||||
|
||||
private fun traverseOuterInstanceReferences(
|
||||
method: KtNamedFunction,
|
||||
body: (SourceInstanceReferenceUsageInfo) -> Unit = {}
|
||||
): LinkedHashMap<KtClass, MutableSet<KtNamedDeclaration>> {
|
||||
): Map<KtClass, MutableSet<KtNamedDeclaration>> {
|
||||
val context = method.analyzeWithContent()
|
||||
val containingClassOrObject = method.containingClassOrObject ?: return LinkedHashMap()
|
||||
val containingClassOrObject = method.containingClassOrObject ?: return emptyMap()
|
||||
val descriptor = containingClassOrObject.unsafeResolveToDescriptor()
|
||||
|
||||
fun getClassOrObjectAndMemberReferencedBy(reference: KtExpression): Pair<DeclarationDescriptor?, CallableDescriptor?> {
|
||||
@@ -311,7 +315,7 @@ private fun traverseOuterInstanceReferences(
|
||||
return classOrObjectDescriptor to memberDescriptor
|
||||
}
|
||||
|
||||
val thisClassesToMembers = LinkedHashMap<KtClass, MutableSet<KtNamedDeclaration>>()
|
||||
val thisClassesToMembers = mutableMapOf<KtClass, MutableSet<KtNamedDeclaration>>()
|
||||
method.bodyExpression?.forEachDescendantOfType<KtExpression> { reference ->
|
||||
val (classOrObjectDescriptor, memberDescriptor) = getClassOrObjectAndMemberReferencedBy(reference)
|
||||
(classOrObjectDescriptor?.findPsi() as? KtClassOrObject)?.let { resolvedClassOrObject ->
|
||||
|
||||
Reference in New Issue
Block a user