From ed67517fb9831d624c0a1b48eb71e2b5503bd1ef Mon Sep 17 00:00:00 2001 From: aleksandrina-streltsova Date: Mon, 8 Jun 2020 17:05:03 +0300 Subject: [PATCH] Move: make minor refactorings and add GUI form for MoveKotlinMethodDialog --- .../moveMethod/MoveKotlinMethodDialog.form | 88 ++++++ .../moveMethod/MoveKotlinMethodDialog.java | 299 ++++++++++++++++++ .../move/moveMethod/MoveKotlinMethodDialog.kt | 290 ----------------- .../moveMethod/MoveKotlinMethodHandler.kt | 44 +-- .../moveMethod/MoveKotlinMethodProcessor.kt | 10 +- 5 files changed, 419 insertions(+), 312 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.form create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.java delete mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.form b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.form new file mode 100644 index 00000000000..eec0486252c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.form @@ -0,0 +1,88 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.java new file mode 100644 index 00000000000..7d1057456f4 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.java @@ -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 variableToClassMap; + private final KtNamedDeclaration[] variables; + private final Map> thisClassesToMembers; + private final KtClassOrObject targetContainer; + private final Map oldClassParameterNameFields; + private KtNamedDeclaration selectedTarget; + private JPanel mainPanel; + private JRadioButton toClassRadioButton; + private JRadioButton toObjectRadioButton; + private JPanel targetObjectChooserPanel; + private KotlinTypeReferenceEditorComboWithBrowseButton targetObjectChooser; + private JList targetVariableList; + private JPanel targetVariableListPanel; + private JPanel parametersPanel; + private JCheckBox openInEditorCheckBox; + + public MoveKotlinMethodDialog( + @NotNull KtNamedFunction method, + @NotNull Map 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 oldClassParameterNames = new LinkedHashMap<>(); + for (Map.Entry 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 listModel = new AbstractListModel() { + @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 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 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()); + } + }; + } +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.kt deleted file mode 100644 index af34d7f14fa..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodDialog.kt +++ /dev/null @@ -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, - 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() - 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() - 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 { - val listModel = object : AbstractListModel() { - 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" - } -} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodHandler.kt index 903e04be004..3224d3bd950 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodHandler.kt @@ -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 { @@ -55,7 +64,7 @@ class MoveKotlinMethodHandler : MoveHandlerDelegate() { val variableToClassMap = LinkedHashMap() 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().forEach { userType -> - if (userType.referenceExpression?.mainReference?.resolve() in typeParameters) return true - } - return false + return collectDescendantsOfType().any { userType -> userType.referenceExpression?.mainReference?.resolve() in typeParameters } } override fun getActionName(elements: Array): String = "${KotlinBundle.message("text.move.method")}.." diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodProcessor.kt index 448153338a6..8cb55f35c5a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodProcessor.kt @@ -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> { +): Map> { 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 { @@ -311,7 +315,7 @@ private fun traverseOuterInstanceReferences( return classOrObjectDescriptor to memberDescriptor } - val thisClassesToMembers = LinkedHashMap>() + val thisClassesToMembers = mutableMapOf>() method.bodyExpression?.forEachDescendantOfType { reference -> val (classOrObjectDescriptor, memberDescriptor) = getClassOrObjectAndMemberReferencedBy(reference) (classOrObjectDescriptor?.findPsi() as? KtClassOrObject)?.let { resolvedClassOrObject ->