Move: make minor refactorings and add GUI form for MoveKotlinMethodDialog

This commit is contained in:
aleksandrina-streltsova
2020-06-08 17:05:03 +03:00
committed by Igor Yakovlev
parent 5515465760
commit ed67517fb9
5 changed files with 419 additions and 312 deletions
@@ -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>
@@ -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());
}
};
}
}
@@ -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"
}
}
@@ -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")}.."
@@ -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 ->